何时需要多线程?
程序需要同时执行两个或多个任务。
程序实现一些需要等待的任务时,如用户输入、文件读写操作、网络操作、搜索。
需要一些后台运行的程序时。比如 垃圾回收。
线程的创建方式一 -- 通过继承Thread类创建线程类
package com.sheting.thread.demo1;
/**
* Create Time: 2018-03-11 06:02
*
* @author sheting
*/
public class TestThread {
public static void main(String[] args) {
//创建一个线程
SubTread subTread = new SubTread();
//启动线程
subTread.start();
//一个线程的实例只能调用一次start()方法启动一个线程,如果要启动用多个线程,要创建多个线程实例,各自调用start()方法
//subTread.start();
//不能直接运行run方法,这样没有启动一个线程,只是普通类,调用了普通方法。
//subTread.run();
//主线程
for (int i = 0; i < 100; i++) {
System.out.println(String.format("%s--%s",Thread.currentThread().getName(),i));
}
}
}
/**
* 通过继承Thread类,重写run方法 创建线程类。
*/
class SubTread extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(String.format("%s--%s",Thread.currentThread().getName(),i));
}
}
}
线程常用的方法
- start() 启动线程并执行run方法
- run() 子线程要执行的代码放入run()方法中
- currentThread() 静态的,调取当前的线程
- getName() 获取线程的名字
- setName() 设置线程的名字
- yield() 调用此方法的线程释放当前CPU的执行权
- join() 在A线程中调用B线程的join()方法,表示:当执行到此方法,A线程停止执行,直至B线程执行完毕。
- isAlive() 判断当前线程是否存活
- sleep(long l) 显示的让当前线程睡眠指定毫秒数
- 线程通信 wait() nofigy() natifyAll()
- 设置线程的优先级 setPriority(), getPriority()
package com.sheting.thread.demo1;
/**
* Create Time: 2018-03-11 06:02
* 线程常用的方法
* @author sheting
*/
public class TestThread {
public static void main(String[] args) {
//创建一个线程
SubTread subTread = new SubTread();
//setName()设置线程名称
subTread.setName("子线程");
//设置线程的优先级(优先级从1到10, 默认是5。 数字越大抢占到CPU的执行权的机会变大)
subTread.setPriority(Thread.MAX_PRIORITY);
//启动线程,并执行 run()方法
subTread.start();
//主线程名字设置
Thread.currentThread().setName("主线程");
/*
for (int i = 0; i < 1000; i++) {
System.out.println(String.format("%s--%s",Thread.currentThread().getName(),i));
if(i%10 == 0){
//不应该通过类实例访问静态成员(避免通过一个类的对象引用访问此类的静态变量或静态方法,无谓增加编译器解析成本,直接用类名来访问)
//Thread.currentThread().yield();
//调用此方法的线程释放当前CPU的执行权,但是也有可能重新被当前线程获取CPU执行权
Thread.yield();
}
}*/
for (int i = 0; i < 100; i++) {
System.out.println(String.format("%s--%s", Thread.currentThread().getName(), i));
if (i == 20) {
try {
//在A线程中调用B线程的join()方法,表示当执行到此方法,线程A停止执行,直至B线程执行完成。
subTread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//判断当前线程是否存活
boolean alive = subTread.isAlive();
System.out.println(alive);
}
}
/**
* 通过继承Thread类,重写run方法 创建线程类。
*/
class SubTread extends Thread {
/**
* 子线程要执行的代码,放入run()方法
*/
@Override
public void run() {
for (int i = 0; i < 300; i++) {
try {
//显示指定当前线程睡眠多少毫秒
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//currentThread() 静态的,获取当前线程
//getName() 获取线程的名字
System.out.println(String.format("%s--%s", Thread.currentThread().getName(), i));
}
}
}
线程的创建方式二 --实现Runnable接口
package com.sheting.thread.demo1;
/**
* Create Time: 2018-03-13 06:10
*
* @author sheting
*/
public class TestRunnable {
public static void main(String[] args) {
//启动一个线程,必须调用start()
PrintNum printNum = new PrintNum();
Thread thread = new Thread(printNum);
thread.start();
//再启动一个线程
Thread thread2 = new Thread(printNum);
thread2.start();
}
}
class PrintNum implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
System.out.println(String.format("%s: %s", Thread.currentThread().getName(), i));
}
}
}
}
继承Thread 和 实现Runnable比较
- 1.
public class Thread implements Runnable {}
Thread 也实现了Runnable接口 -
- 实现的方式 优于继承的方式
(1) 避免了Java单继承的局限性
(2) 如果多个线程要操作同一份资源(数据),更适合使用实现的方式
比如 如下代码 printNum对象只new了一次, thread和 thread2两个线程共享printNum对象的成员属性等
- 实现的方式 优于继承的方式
PrintNum printNum = new PrintNum();
Thread thread = new Thread(printNum);
Thread thread2 = new Thread(printNum);
thread.start();
thread2.start();
多线程程序的优点
(1)提供应用程序的响应。对图形化界面更有意义,可增强用户体验。
(2) 提供计算机系统CPU的利用率。
(3) 改善程序结构。将既长又负载的进程分为多个线程,独立运行,利于理解和修改。
守护线程
Java中的线程分为两类:一种是守护线程, 一种是用户线程。
守护线程是用来服务用户线程的,通过在start()方法前调用thread.setDaemon(true)
可以把一个用户线程变为一个守护线程。
守护线程和用户线程的没啥本质的区别:唯一的不同之处就在于虚拟机的离开:如果用户线程已经全部退出运行了,只剩下守护线程存在了,虚拟机也就退出了。 因为没有了被守护者,守护线程也就没有工作可做了,也就没有继续运行程序的必要了。Java垃圾回收就是一个典型的守护线程。
在使用守护线程时需要注意一下几点:
(1) thread.setDaemon(true)必须在thread.start()之前设置,否则会跑出一个IllegalThreadStateException异常。你不能把正在运行的常规线程设置为守护线程。
(2) 在Daemon线程中产生的新线程也是Daemon的。
(3) 守护线程应该永远不去访问固有资源,如文件、数据库,因为它会在任何时候甚至在一个操作的中间发生中断。
线程的生命周期
新建:当一个Thread类或其自雷的对象被声明并创建时,新生的线程对象处于新建状态。
就绪:处于新建状态的线程被start()后,将进入线程队列等待CPU时间片,此时它已具备了运行的条件。
运行: 当就绪的线程被调度并获得处理器资源时,便进入运行状态,run()方法定义了线程的操作和功能。
阻塞:在某种特殊情况下,被人为挂起或执行输入输出操作时,让出CPU并临时中止自己的执行,进入阻塞状态
死亡:线程完成了它的全部工作或线程被提前强制性中止。
synchronized关键字
线程安全问题存在的原因?
由于一个线程在操作共享数据过程中,未执行完毕的情况下,另外的线程参与进来,导致共享数据存在安全问题。
如何解决线程的安全问题?
必须让一个线程操作共享数据完毕以后,其他线程才有机会参与共享数据的操作。
多线程只有有共享数据,才涉及同步。
Java如何实现线程的安全:线程的同步机制
方式一: 同步代码块
synchronized(同步监视器){
//需要被同步的代码块(即 操作共享数据的代码)
}
共享数据: 多个线程共同操作的同一个数据(变量)
同步监视器:由一个类的对象来充当。那个线程获取此监视器,它就执行大括号里被同步的代码。俗称 锁
特别重要,重在理解
注意:所有的线程要共用同一把锁(也就是同一个对象)。
在实现Runnable接口的方式中,考虑同步的话,可以使用this来充当锁。但是在继承的方式中,慎用this, 而且锁对象最好是静态的全局变量。
方式二:同步方法
将操作共享数据的方法声明为synchronized。即此方法为同步方法,能够保证 当其中一个线程执行此方法时,其它线程在外等待直至此线程执行完此方法。
同步方法的锁:this 因此同步方法不一定能保证线程安全,必须同步方法也必须是同一个this.
线程同步的弊端:由于同一个时间只能有一个线程访问共享数据,效率变低了。
释放锁的操作:
- 当前线程的同步方法、同步代码块执行结束
- 当前线程在同步代码块、同步方法中遇到break、return终止了代该代码块、该方法的继续执行。
- 当前线程在同步代码块、同步方法中出现了未处理的Error或Exception,导致异常结束。
- 当前线程在同步代码块、同步方法中执行了线程对象的wait()方法,当前线程暂停,并释放锁。
不会释放锁的操作:
- 线程执行同步代码块或同步方法时,程序调用Thread.sleep()/Thread.yield()方法暂停当前线程的执行。
- 线程执行同步代码块时,其他线程调用了该线程的suspend()方法将该线程挂起,该线程不会释放锁。
应尽量避免使用suspend()和resume()来控制线程。
单例模式之懒汉式线程安全
package com.sheting.singleton;
/**
* Create Time: 2018-03-14 08:50
*
* @author sheting
*/
public class Singleton {
private Singleton() {
}
private static Singleton instance = null;
public static Singleton getInstance() {
//if的作用是当多个线程时,如果已经实例化了instance,就不需要等待
if(instance == null){
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
线程同步练习
线程的通信
wait() 令当前线程挂起并放弃CPU、同步资源,使别的线程可访问并修改共享资源,而当前线程排队等候再次对资源的访问。
notify() 唤醒正在排队等待同步资源的线程中优先级最高者结束等待。
notifyAll() 唤醒正在排队等待资源的所有线程结束等待。
java.lang.Object提供的这三个方法只有在synchronized方法或者synchronized代码块中才能使用,否则会报java.lang.IllegalMonitorStateException异常
package com.sheting.thread.demo;
/**
* Create Time: 2018-03-16 05:54
*
* @author sheting
*/
public class TestCommunication {
public static void main(String[] args) {
PrintNum printNum = new PrintNum();
Thread thread1 = new Thread(printNum);
Thread thread2 = new Thread(printNum);
thread1.start();
thread2.start();
}
}
class PrintNum implements Runnable {
int num = 1;
@Override
public void run() {
while (true) {
synchronized (this) {
notify();
if (num < 100) {
System.out.println(String.format("%s:%s", Thread.currentThread().getName(), num));
num++;
} else {
break;
}
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
生产者消费者
package com.sheting.thread.demo;
/**
* Create Time: 2018-03-16 07:00
*
* @author sheting
*/
public class TestProductConsume {
public static void main(String[] args) {
Dispatcher dispatcher = new Dispatcher();
Producer producer = new Producer(dispatcher);
Consumer consumer = new Consumer(dispatcher);
new Thread(producer).start();
new Thread(consumer).start();
}
}
class Producer implements Runnable{
Dispatcher dispatcher;
public Producer(Dispatcher dispatcher){
this.dispatcher = dispatcher;
}
@Override
public void run() {
while (true){
dispatcher.addProducer();
}
}
}
class Consumer implements Runnable{
Dispatcher dispatcher;
public Consumer(Dispatcher dispatcher){
this.dispatcher = dispatcher;
}
@Override
public void run() {
while (true){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
dispatcher.consumeProduct();
}
}
}
class Dispatcher{
int producerNum;
public synchronized void addProducer() {
if(producerNum >= 20){
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else {
producerNum++;
System.out.println(String.format("%s:生成了第%s个产品", Thread.currentThread().getName(), producerNum));
notifyAll();
}
}
public synchronized void consumeProduct(){
if(producerNum <= 0){
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else {
System.out.println(String.format("%s:消费了第%s个产品", Thread.currentThread().getName(), producerNum));
producerNum--;
notifyAll();
}
}
}
线程的死锁
死锁:不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁。
package com.sheting.thread.demo;
/**
* Create Time: 2018-03-16 07:36
*
* @author sheting
*/
public class DeadLock {
static StringBuffer sb1 = new StringBuffer();
static StringBuffer sb2 = new StringBuffer();
public static void main(String[] args) {
new Thread(){
@Override
public void run() {
synchronized (sb1){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
sb1.append("A");
synchronized (sb2){
sb2.append("B");
}
}
}
}.start();
new Thread(){
@Override
public void run() {
synchronized (sb2){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
sb1.append("C");
synchronized (sb1){
sb2.append("D");
}
}
}
}.start();
}
}