多线程
0.基本概念
程序 :为完成特定任务,用某种编程语言编写的一组指令的集合(静态
)
进程 :程序的一次执行过程,或正在执行的一个程序(动态
过程)
线程:程序内部的一条执行路径,若某个程序支持同一时间执行多个线程,即支持多线程
1.多线程的创建和使用
继承Thread类创建多线程
- 一个线程只能执行一次
start()
- 不能通过
Thread
类的run
方法去启动一个线程
例:
java
package thead;
//1.创建继承于Thread的子类
class SubThread extends Thread{
//2.重写Thread类的run方法,方法内实现此子类线程要完成的功能
public void run() {
for(int i = 1;i <= 100;i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
public class test1 {
public static void main(String[] args) {
//4.创建子类对象
SubThread s = new SubThread();
//5.调用start()方法,启动线程;再调用相应的run方法
s.start();
//主线程执行
for(int i = 1;i <= 100;i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
实现Runnable接口创建多线程
package thead;
//创建多线程的方式二:通过实现的方式
//1.创建实现Runnable接口的类
class PrintNum implements Runnable{
//2.重写run的方法
public void run() {
for(int i = 1;i <= 100;i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
public class test4 {
public static void main(String[] args) {
//3.创建一个Runnable接口的实现类的对象
PrintNum p = new PrintNum();
//4.将此对象作为形参传递给Thread类的构造器中
Thread t1 = new Thread(p);//想启动多线程,必须调用start()
t1.start();
Thread t2 = new Thread(p);
t2.start();
}
}
继承Thread类与实现Runnable接口的区别
- 1.都继承
了Runnable
接口(Thread类实现Runnable
接口) - 2.实现的方式优于继承的方式(避免Java单线程的局限性;如果多个线程要操作同一份资源(或数据),更合适使用实现的方式)
案例:模拟火车站售票,开启三个窗口,总票数100张
继承
java
package project;
//模拟火车站售票,开启三个窗口,总票数100张
class Window extends Thread{
static int ticket = 100; //总票数
public void run() {
while(true) {
if(ticket > 0) {
System.out.println(Thread.currentThread().getName() + "售票口:票号为:" + ticket--);
}else {
break;
}
}
}
}
public class test1 {
public static void main(String[] args) {
Window w1 = new Window();
Window w2 = new Window();
Window w3 = new Window();
w1.setName("1号");
w2.setName("2号");
w3.setName("3号");
w1.start();
w2.start();
w3.start();
}
}
实现
java
package project;
//模拟火车站售票,开启三个窗口,总票数100张
class Window1 implements Runnable{
int ticket = 100; //总票数
public void run() {
while(true) {
if(ticket > 0) {
System.out.println(Thread.currentThread().getName() + "售票口:票号为:" + ticket--);
}else {
break;
}
}
}
}
public class test2 {
public static void main(String[] args) {
Window1 w = new Window1();
Thread t1 = new Thread(w);
Thread t2 = new Thread(w);
Thread t3 = new Thread(w);
t1.setName("1号");
t2.setName("2号");
t3.setName("3号");
t1.start();
t2.start();
t3.start();
}
}
多线程的优点
- 1.提高应用程序的响应,对图形化界面更有意义,可增强用户体验
- 2.提供计算机CPU利用率
- 3.改善程序结构,将复杂的进程分为多个线程,独立运行,利于理解和修改
Thread类的主要方法与配置线程优先级
java
package thead;
/*
* 线程的主要方法
* 1.start():启动线程并执行相应的run()方法
* 2.run():子线程要执行的代码放入run()方法中
* 3.currentThread():静态的,调取当前的线程
* 4.getName():获取线程的名字
* 5.setName():设置线程名
* 6.yield():调用此方法的线程释放当前CPU的执行权
* 7.join():在A线程中调用B线程的join()方法,当执行到此方法时,A停止,B执行,等B全部执行完后,A再继续执行剩下的任务。
* 8.isAlive():检测线程是否存活
* 9.sleep(long l):显式的让当前线程睡眠l毫秒
* 10.线程通信:wait() notify() notifyAll()后续介绍
* 11.线程优先级(默认5,最大10,最小1):只是提高了抢到的CPU执行权的概率
* getPriority():返回线程的优先级
* setPriority(int newPriority):改变线程的优先级
*/
class SubThread1 extends Thread{
public void run() {
for(int i = 1;i <= 100;i++) {
// try {
// Thread.currentThread().sleep(1000);
// } catch (InterruptedException e) {
// // TODO Auto-generated catch block
// e.printStackTrace();
// }
System.out.println(Thread.currentThread().getName() + ":" + Thread.currentThread().getPriority() + ":" + i);
}
}
}
public class test2 {
public static void main(String[] args) {
SubThread1 s1 = new SubThread1();
s1.setName("线程1号");
s1.setPriority(Thread.MAX_PRIORITY);
s1.start();
Thread.currentThread().setName("======主线程=====");
for(int i = 1;i <= 100;i++) {
System.out.println(Thread.currentThread().getName() + ":" + Thread.currentThread().getPriority() + ":" + i);
// if(i % 10 == 0) {
// Thread.currentThread().yield();
// }
// if(i == 10) {
// try {
// s1.join();
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// }
}
System.out.println(s1.isAlive());
}
}
练习
java
package thead;
class SuThread1 extends Thread{
//1-100奇数
public void run() {
for(int i = 1;i <= 100;i = i + 2) {
System.out.println(Thread.currentThread().getName() + " : " + i);
}
}
}
class SuThread2 extends Thread{
//1-100偶数
public void run() {
for(int i = 2;i <= 100;i = i + 2) {
System.out.println(Thread.currentThread().getName()+ " : " + i);
}
}
}
class test3{
public static void main(String[] args) {
SuThread1 st1 = new SuThread1();
SuThread2 st2 = new SuThread2();
st1.setName("子线程1号");
st1.start();
st2.setName("子线程2号");
st2.start();
// //继承Thread类的匿名类的对象
// new Thread() {
// public void run() {
// for(int i = 2;i <= 100;i = i + 2) {
// System.out.println(Thread.currentThread().getName()+ " : " + i);
// }
// }
// }.start();
}
}
2.线程的生命周期
生命周期中通常要经历的5种状态
新建 :当一个Thread类或子类的对象被声明并创建时,新生的线程对象处于新建状态
就绪 :处于新建状态的线程被start()后,将进入线程队列等待CPU时间片,此时它已具备运行的条件
运行 :当就绪的线程被调度并获得处理器资源时,便进入运行状态,run()方法定义了线程的操作和功能
阻塞 :在某种特殊情况下,被人为挂起或执行输入输出操作时,让出CPU并临时中止自己的执行,进入阻塞状态
死亡 :线程完成全部工作或线程被提前强制中止
3.线程的同步(重点)
在上面列举的火车售票案例中存在售错票,重票 等的情况,存在线程安全问题
原因 :由于线程在操作共享数据过程中,未执行完毕的情况下,另外的线程参与进来,导致共享的数据存在安全问题
解决 :必须让一个线程操作共享数据 完毕以后,其他线程才有机会参与共享数据的操作
Java利用线程同步来解决线程安全问题
方法一:同步代码块
java
synchronized(同步监视器){
//需要同步的代码(即为操作共享数据的代码);
}
- 1.同步监视器:由一个任意类的对象来充当。哪个线程获此监视器,谁就执行括号中被同步的代码。俗称:锁
- 2.共享数据:多线程共同操作的同一个数据(变量)
- 要求:所有的线程必须共用同一把锁
例:
java
package project;
//模拟火车站售票,开启三个窗口,总票数100张
class Window2 implements Runnable{
int ticket = 100; //总票数(共享数据)
Object obj = new Object();
public void run() {
while(true) {
synchronized(obj){
if(ticket > 0) {
try {
Thread.currentThread().sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "售票口:票号为:" + ticket--);
}else {
break;
}
}
}
}
}
public class test4 {
public static void main(String[] args) {
Window2 w = new Window2();
Thread t1 = new Thread(w);
Thread t2 = new Thread(w);
Thread t3 = new Thread(w);
t1.setName("1号");
t2.setName("2号");
t3.setName("3号");
t1.start();
t2.start();
t3.start();
}
}
注:在实现方式中可以用this表示当前对象(同步监视器),若在继承的方式中慎用this
方法二:同步方法
java
public synchronized void show() {
//需要同步的代码(即为操作共享数据的代码);
}
例:
java
package project;
class Window3 implements Runnable{
int ticket = 100;
public void run() {
while(true) {
show();
}
}
public synchronized void show() {
if(ticket > 0) {
try {
Thread.currentThread().sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "售票口:票号为:" + ticket--);
}
}
}
public class test3 {
public static void main(String[] args) {
Window3 w = new Window3();
Thread t1 = new Thread(w);
Thread t2 = new Thread(w);
Thread t3 = new Thread(w);
t1.setName("1号");
t2.setName("2号");
t3.setName("3号");
t1.start();
t2.start();
t3.start();
}
}
互斥锁
单例之懒汉式的线程安全
例:
java
package thead;
/* 单例之懒汉式的线程安全:使用同步机制 */
//对于静态方法而言,使用当前类本身充当锁
class Singletion{
private Singletion() {
}
private static Singletion instance = null;
public static Singletion getInstance() {
if(instance == null) {
synchronized(Singletion.class) {
if(instance == null) {
instance = new Singletion();
}
}
}
return instance;
}
}
public class test5 {
public static void main(String[] args) {
// TODO Auto-generated method stub
Singletion s1 = Singletion.getInstance();
Singletion s2 = Singletion.getInstance();
System.out.println(s1 == s2);
}
}
- 线程同步的弊端:同一时间只能有一个线程访问共享数据,效率变低
练习
java
package project;
//线程安全练习
//存在多线程,存在共享数据,需同步
class Account{
double balance;//余额
public Account() {
}
//存取
public synchronized void deposit(double amt) {
balance += amt;
try {
Thread.currentThread().sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":" + balance );
}
}
class Customer extends Thread{
Account account;
public Customer(Account account) {
this.account = account;
}
public void run() {
for(int i = 0;i < 3;i++) {
account.deposit(1000);
}
}
}
public class test5{
public static void main(String[] args) {
Account acct = new Account();
Customer c1 = new Customer(acct);
Customer c2 = new Customer(acct);
c1.setName("甲");
c2.setName("乙");
c1.start();
c2.start();
}
}
线程死锁
例:
java
package thead;
/* 死锁:处理线程同步时容易出现 */
//一个线程抓住一半锁,另一个线程抓住一半锁,互不相让
public class test6 {
static StringBuffer sb1 = new StringBuffer();
static StringBuffer sb2 = new StringBuffer();
public static void main(String[] args) {
new Thread() {
public void run() {
synchronized(sb1) {
try {
Thread.currentThread().sleep(10);
}catch(InterruptedException e) {
e.printStackTrace();
}
}
sb1.append("A");
synchronized(sb2){
sb2.append("B");
System.out.println(sb1);
System.out.println(sb2);
}
}
}.start();
new Thread() {
public void run() {
synchronized(sb2) {
try {
Thread.currentThread().sleep(10);
}catch(InterruptedException e) {
e.printStackTrace();
}
}
sb1.append("C");
synchronized(sb1){
sb2.append("D");
System.out.println(sb1);
System.out.println(sb2);
}
}
}.start();
}
}
4.线程的通信
wait()
让当前线程挂起并放弃CPU,同步资源,使用别的线程可访问并修改共享资源,而当前线程排队等候再次对资源的访问
notify()
唤醒正在排队等待同步资源的线程中优先级最高者结束等待
ontifyAll()
唤醒正在排队等待资源的所有线程结束等待
以上三个方法(Java.lang.Object
提供)只有在synchronized()
方法或synchronized
代码块中才能使用,否则会报异常
例1:
java
package thead;
/* 两个线程交替打印1~100的数 */
//线程通信:wait(),让当前线程停止;notify(),notifyAll()唤醒wait()停止的1个或多个线程
class PrintfNum implements Runnable{
int num = 1;
public void run() {
while(true) {
synchronized (this) {//解决线程安全问题
notify();
if (num <= 100) {
try {
Thread.currentThread().sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} //使隐藏的线程安全问题暴露处理
System.out.println(Thread.currentThread().getName() + ":" + num);
num += 1;
} else {
break;
}
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
public class test7 {
public static void main(String[] args) {
PrintfNum p = new PrintfNum();
Thread t1 = new Thread(p);
Thread t2 = new Thread(p);
t1.setName("A");
t2.setName("B");
t1.start();
t2.start();
}
}
生产者/消费者问题
生产者(Productor)将产品交给店员(Clerk),而消费者(Customer)从店员处取走。店员一次只能持有一定数量的产品(如:20).如果生产者试图多生产会被叫停,如果店中有空位才会通知生产者生产;如果店中没有产品,店员会告诉消费者等一下,有产品后在通知消费者取走产品。
注:可能会出现的问题:
-
1.生产者比消费者快,消费者会漏掉一些数据没有取到
-
2.消费者比生产者快,消费者会取到相同的数据
java
package thead;
//生产者/消费者问题
//多线程:生产者,消费者;共享数据:产品数量;存在数据通信
//店员
class Clerk{
int product;
public synchronized void addProduct() {
if(product < 20) {
product += 1;
System.out.println(Thread.currentThread().getName() + ":生产到第" + product + "产品了!" );
notify();
}else
try {
wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public synchronized void consumProduct() {
if(product <= 0) {
try {
wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}else {
System.out.println(Thread.currentThread().getName() + ":消费到第" + product + "产品了!" );
product -= 1;
notify();
}
}
}
//生产者
class Producer implements Runnable{
Clerk clerk;
public Producer(Clerk clerk) {
this.clerk = clerk;
}
public void run() {
System.out.println("生产者开始生产产品");
while(true) {
try {
Thread.currentThread().sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
clerk.addProduct();
}
}
}
//消费者
class Customer implements Runnable{
Clerk clerk;
public Customer(Clerk clerk) {
this.clerk = clerk;
}
public void run() {
System.out.println("消费者开始消费产品");
while(true) {
try {
Thread.currentThread().sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
clerk.consumProduct();
}
}
}
public class test8 {
public static void main(String[] args) {
// TODO Auto-generated method stub
Clerk clerk = new Clerk();
Producer producer = new Producer(clerk);
Customer customer = new Customer(clerk);
Thread t1 = new Thread(producer);//一个生产者线程
Thread t3 = new Thread(producer);//一个生产者线程
Thread t2 = new Thread(customer);//一个生产者线程
t1.setName("生产1号");
t2.setName("消费者");
t3.setName("生产2号");
t1.start();
t2.start();
t3.start();
}
}
练习
在上次练习的基础上,实现交替存款
java
package project;
//线程安全练习
//存在多线程,存在共享数据,需同步
//扩展实现交替打印
class Account{
double balance;//余额
public Account() {
}
//存取
public synchronized void deposit(double amt) {
notify();
balance += amt;
try {
Thread.currentThread().sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":" + balance );
try {
wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
class Customer extends Thread{
Account account;
public Customer(Account account) {
this.account = account;
}
public void run() {
for(int i = 0;i < 3;i++) {
account.deposit(1000);
}
}
}
public class test5{
public static void main(String[] args) {
Account acct = new Account();
Customer c1 = new Customer(acct);
Customer c2 = new Customer(acct);
c1.setName("甲");
c2.setName("乙");
c1.start();
c2.start();
}
}
感谢大家的支持,关注,评论,点赞!
参考资料:
尚硅谷宋红康20天搞定Java基础下部