一个线程是指程序中完成一个任务的执行流,java中可以在一个程序中并发地运行多个线程,这些线程可以同时在多个处理器上运行 在单CPU系统中,多个线程分享CPU的时间,操作系统负责CPU资源的调度和分配
多线程可以使程序的反应更快,交互性更强,执行效率更高。当程序作为一个应用程序(application)运行时,jvm会为main方法创建一个线程,当程序不再需要时,jvm就会创建一个线程来进行垃圾回收的工作。所以一个完整的应用程序最少含有两个线程。
Note:因为java是单继承、多实现的,所以建议使用实现Runnable接口的方法来实现线程
考虑以下代码是哪一个线程睡眠了?
public class MyThread2 extends Thread { //自定义线程 public void run() { for(int i=0;i<100;i++){ System.out.println(i); } } //主线程 public static void main(String[] args) throws InterruptedException { MyThread2 thread2 = new MyThread2(); thread2.sleep(5 * 1000); thread2.setName("线程-A"); thread2.start(); } }“thread2.sleep(5 * 1000);”是在主线程中执行,所以主线程会被睡眠
public class MyThread2 extends Thread { //自定义线程 public void run() { try { Thread.sleep(2 * 1000); } catch (InterruptedException e) { e.printStackTrace(); } for(int i=0;i<100;i++){ System.out.println(i); } } //主线程 public static void main(String[] args) throws InterruptedException { MyThread2 thread2 = new MyThread2(); thread2.start(); } }“Thread.sleep(2 * 1000);”是在自定义线程中执行,所以自定义线程会被睡眠 3. 得到当前线程对象 静态方法,哪个线程执行了含有currentThread()的代码,就返回哪个线程的对象
public class MyThread2 extends Thread { public MyThread2(String name) { super(name); } //自定义线程 public void run() { System.out.println("this:"+this); System.out.println("2:"+Thread.currentThread()); } //主线程 public static void main(String[] args) throws InterruptedException { MyThread2 thread2 = new MyThread2("线程-B"); thread2.start(); System.out.println("1:"+thread2.currentThread()); } }运行后控制台输出如下:
1:Thread[main,5,main] this:Thread[线程-B,5,main] 2:Thread[线程-B,5,main] 4. 设置线程的优先级 优先级越高的线程获取CPU资源的几率越大
//设置优先级1~10之间,数值越大优先级越高,默认的优先级为5 thread2.setPriority(10);优先级越高的线程不会100%优先执行,只是优先执行的概率更大而已 5. isAlive() isAlive()是用来判断线程的状态,当线程处于就绪、临时阻塞、运行状态,则返回true;如果线程处于新建并且没有启动的状态,或者线程已经执行结束,则返回false.
单一线程时,它只能在同一时间进行一项操作,所以永远不必担心有两个实体同时使用相同的资源。但是进入多线程环境后,它们就不再是孤立的,可能多个线程试图在同一时间访问同一个资源。比如两个线程同时从一个银行账户取款!
举一个多线程中常见的例子,三个售票窗口同时卖票,一共50张票,使用以下代码模拟:
class SaleTicket extends Thread { // 设置为静态变量,因为是三个线程的贡献资源数据 static int num = 50; public SaleTicket(String name) { super(name); } @Override public void run() { while (true) { if (num > 0) { System.out.println(Thread.currentThread().getName() + "售出了" + num + "号票"); num--; } else { System.out.println("售罄"); break; } } } } public class Demo1 { public static void main(String[] args) { SaleTicket a = new SaleTicket("A窗口"); SaleTicket b = new SaleTicket("B窗口"); SaleTicket c = new SaleTicket("C窗口"); a.start(); b.start(); c.start(); } }控制台输出如下:
A窗口售出了50号票 B窗口售出了50号票 B窗口售出了48号票 ... ... ...可以发现50号票被A、B窗口都卖出了,显然是错误的。这里就出现了线程安全问题,分析如下: 由控制台的输出我们可以做以下假设: 假设A窗口先抢到了CPU的资源,在第5行判断num>0,打印第6行的内容,此时CPU的资源被B窗口抢走,注意A窗口并没有执行num–,然后B窗口在第5行判断num>0,打印第6行的内容,执行num–,此时num等于49,然后CPU的资源被A窗口抢走,此时A窗口应该执行第6行代码,num–。现在num = 48,B窗口抢夺了CPU的资源,判断num>0,执行第6行,就会打印出”B窗口售出了48号票”。
在什么情况下会出现线程安全问题? **
存在多线程存在共享的资源(比如上例中的票)存在多个任务来操作共享资源,当前任务进行到一半时,CPU的资源被别的线程抢夺**
线程安全问题的解决办法: 同步机制:调用synchronized方法时对象会被锁定,不能被其他的线程访问,除非当前线程完成了被同步的任务,并解除锁定。 ①:同步代码块
synchronized("锁对象"){ //需要被同步的代码 }同步代码块要注意的事项: - “锁对象”可以是任意的一个对象 - 多线程操作的锁对象必须是唯一的、共享的 - 在同步代码块中调用sleep方法,并不会释放锁 - 只有存在线程安全问题的时候才使用同步代码块,否则会降低线程执行的效率
使用同步代码块完成卖票,并解决线程安全问题:
class SaleTicket extends Thread { // 设置为静态变量,因为是三个线程的贡献资源数据 static int num = 50; static Object o = new Object(); public SaleTicket(String name) { super(name); } @Override public void run() { while (true) { // 同步代码块 synchronized (o) { if (num > 0) { System.out.println(Thread.currentThread().getName() + "售出了" + num + "号票"); num--; } else { System.out.println("售罄"); break; } } } } } public class Demo1 { public static void main(String[] args) { SaleTicket a = new SaleTicket("A窗口"); SaleTicket b = new SaleTicket("B窗口"); SaleTicket c = new SaleTicket("C窗口"); a.start(); b.start(); c.start(); } }②:同步函数 - 使用synchronized 修饰函数 - 如果是非静态的同步函数,锁对象是当前对象;如果是静态的同步函数,锁对象是当前函数所属的类的class对象。 考虑以下代码能不能解决线程安全的问题?
class BankThread extends Thread { static int count = 5000; public BankThread(String name) { super(name); } @Override public synchronized void run() { while (true) { if (count > 0) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "取走了1000元,还剩余" + (count - 1000) + "元"); count = count - 1000; } else { System.out.println("账户余额不足..."); break; } } } } public class Demo2 { public static void main(String[] args) { BankThread grandFather = new BankThread("爷爷"); BankThread grandMother = new BankThread("奶奶"); grandMother.start(); grandFather.start(); } }控制台输出如下:
爷爷取走了1000元,还剩余4000元 奶奶取走了1000元,还剩余4000元 奶奶取走了1000元,还剩余2000元 爷爷取走了1000元,还剩余1000元 奶奶取走了1000元,还剩余0元 账户余额不足... 爷爷取走了1000元,还剩余-1000元 账户余额不足...因为synchronized 修饰的函数是非静态的,所以当线程grandMother执行取钱任务时,锁对象是grandMother;当grandFather 执行取钱任务时,锁对象是grandFather 对象。锁对象不是唯一的就不能解决线程安全问题
推荐使用同步代码块解决线程安全问题!!!
有时两个或多个线程需要锁定几个共享对象。这时可能引起死锁(deadlock) ,也就是说,每个线程已经锁定一个对象,正在等待锁定另一个对象。考虑一种有两个线程和两个对象的情形。线程1已经锁定了 object1 ,线程2锁定了 object2 。现在线程1等待锁定 object2 ,线程2等待锁定object1 。每个线程都等待另一个线程释放自己需要的资源,结果导致两个线程都无法继续运行。 例如老公拿着银行卡,但是没有密码;老婆记着密码,但是没有银行卡,参考以下代码:
class DeadLock extends Thread { public DeadLock(String name) { super(name); } @Override public void run() { if ("老公".endsWith(Thread.currentThread().getName())) { synchronized ("银行卡") { System.out.println("老公拿到了银行卡,等待密码..."); synchronized ("密码") { System.out.println("老公拿到了密码..."); System.out.println("老公可以去银行取钱了!!!"); } } } else if ("老婆".endsWith(Thread.currentThread().getName())) { synchronized ("密码") { System.out.println("老婆拿到了密码,等待银行卡..."); synchronized ("银行卡") { System.out.println("老婆拿到了银行卡..."); System.out.println("老婆可以去银行取钱了!!!"); } } } } } public class Demo3 { public static void main(String[] args) { DeadLock thread1 = new DeadLock("老公"); DeadLock thread2 = new DeadLock("老婆"); thread1.start(); thread2.start(); } }控制台输出如下:
老公拿到了银行卡,等待密码... 老婆拿到了密码,等待银行卡...老公进入“银行卡”的同步代码块中,但是此时老婆进入了“密码”的同步代码块中。两个线程都会陷入无休止的相互等待状态。尽管这种情况并非经常出现,但一旦碰见,程序的调试就会变得异常艰难。就java语言本身来说,尚未提供防止死锁的措施,我们需要谨慎设计来避免。
但是上述例子中,只要“老公”线程跑的足够快,两者都是可以取到钱的!嘿嘿…. 可以考虑将代码更改为如下,“老公”和“老婆”就有更大的可能性都能取到钱了!
else if ("老婆".endsWith(Thread.currentThread().getName())) { try { Thread.sleep(5*1000); } catch (InterruptedException e) { e.printStackTrace(); } synchronized ("密码") { System.out.println("老婆拿到了密码,等待银行卡..."); synchronized ("银行卡") { System.out.println("老婆拿到了银行卡..."); System.out.println("老婆可以去银行取钱了!!!"); } } }wait():等待,线程执行了wait()方法,就会进入等待状态,等待状态的线程必须要被其他线程调用notify()方法才能唤醒。 notify():唤醒,唤醒等待的线程。 Note: - wait()和notify()属于Object对象的方法 - wait()和notify()必须要在同步代码块或同步函数中才能使用 - wait()和notify()必须要由锁对象调用
当i为偶数时生产者生产苹果,i为奇数是生产者生产香蕉;生产者生产一个产品消费者消费一个产品。使用以下代码进行模拟:
//产品类 class Product { boolean flag = false;// 产品是否已经存在 String name; double price; } // 生产者 class Producer extends Thread { Product p; public Producer(Product p) { this.p = p; } // 不断地生产 @Override public void run() { int i = 0; while (true) { synchronized (p) { if (p.flag == false) { if (i % 2 == 0) { p.name = "苹果"; try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } p.price = 5.0; } else { p.name = "香蕉"; p.price = 2.5; } System.out.println("生产者生产了:" + p.name + "价格是:" + p.price); p.flag = true; //生产完毕,唤醒消费者 p.notify(); i++; } else { try { // 生产者已经生产完毕,等待消费者去消费 p.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } } } } // 消费者 class Customer extends Thread { Product p; public Customer(Product p) { this.p = p; } @Override public void run() { while (true) { synchronized (p) { if (p.flag == true) { System.out.println("消费者消费了:" + p.name + "价格是:" + p.price); p.flag = false; //消费完了,唤醒生产者 p.notify(); }else{ try { //产品没有被生产,等待生产者生产 p.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } } } } public class Demo4 { public static void main(String[] args) { Product p = new Product();// 产品 Producer producer = new Producer(p);// 生产者 Customer customer = new Customer(p);// 消费者 producer.start(); customer.start(); } }控制台输出:
生产者生产了:苹果价格是:5.0 消费者消费了:苹果价格是:5.0 生产者生产了:香蕉价格是:2.5 消费者消费了:香蕉价格是:2.5 ... ...说明: 1. 将p作为构造函数的参数传入,以实现p的共享;并且让p作为锁对象,调用wait和notify 2. 如果一个线程执行了wait()方法,那么该线程就会进入一个以”锁对象”为标识的线程池中并处于等待状态 3. 调用wait()方法会释放锁对象 4. 如果一个线程执行了notify()方法,那么就会唤醒上述线程池中的一个处于等待状态的线程 5. 调用notify()方法不能指定线程来唤醒,一般来说,先等待的线程先被唤醒
使用Thread类的stop()方法来停止一个线程,但此方法已经过时,不推荐使用。 如果需要停止一个处于等待状态的线程,可以通过布尔变量配合notify()或者interrupt()方法。
public class Demo5 extends Thread { boolean falg = true; public Demo5(String name) { super(name); } @Override public synchronized void run() { int i = 0; while (falg) { try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + ":" + i++); } } public static void main(String[] args) { Demo5 d = new Demo5("线程A"); d.setPriority(10); d.start(); for (int i = 0; i < 100; i++) { System.out.println(Thread.currentThread().getName() + ":" + i); // 主线程i=80是,停止线程A if (i == 80) { d.falg = false; /*同步代码块*/ // synchronized (d) { // d.notify(); // } /*使用interrupt清除等待状态,中断线程*/ d.interrupt(); } } } }线程A开启后,flag = true ,线程A会进入等待状态。然后执行主线程,当i=80,flag赋值为false,并且唤醒线程A;因为此时的flag为false,所以run方法里的代码会执行完毕,线程就被停止。 使用interrupt 强制清除处于等待状态的线程:如果线程在调用Object类的wait()、wait(long)或者wait(long,int)方法,或者Thread类的join()、join(long)、join(long,int)、sleep(long)或者sleep(long,int)方法后,执行interrupt会强制清除这些线程!并返回一个InterruptedException的异常。interrupt还可以指定清除哪个线程。
守护线程的作用是在程序运行期间于后台提供一种“常规”服务,但是它并不属于程序的一个基本部分。一旦所有的非守护线程完成,程序就会终止,守护线程也会终止。可以调用isDaemon()查看一个线程是否为守护线程。线程默认不是守护线程,可以使用setDaemon(true)设置一个线程为守护线程。
以下代码为模拟软件更新包的后台下载,
public class Demo6 extends Thread { public Demo6(String name) { super(name); } @Override public void run() { for (int i = 1; i <= 100; i++) { System.out.println("更新包目前下载" + i + "%...."); if (i == 100) { System.out.println("更新包下载完毕,准备安装..."); } } } public static void main(String[] args) { Demo6 d = new Demo6("后台线程"); //设置为守护线程 d.setDaemon(true); System.out.println(d.isDaemon()); d.start(); for(int j = 0;j<100;j++){ System.out.println(Thread.currentThread().getName()+":"+j); } } }可以发现,当主线程停止时,后台下载更新的守护线程也会停止。
控制台输出如下:
妈妈开始做饭 妈妈开始洗菜,切菜,炒菜... 妈妈发现没有酱油了...让我去打酱油 我一直往小卖铺走 打完酱油... 回家,并把酱油交给妈妈... 妈妈继续做饭 全家人开心地吃晚饭...转载于:https://www.cnblogs.com/xpeng-V/p/7349688.html