多线程是指程序中包含多个执行单元,即在一个程序中可以同时运行多个不同的线程来执行不同的任务,也就是说允许单个程序创建多个并行 执行的线程来完成各自的任务。
何时需要多线程
程序需要同时执行两个或多个任务。
程序需要实现一些需要等待的任务时,如用户输入、文件读写操作、 网络操作、搜索等。
需要一些后台运行的程序时。
多线程的优点
提高程序的响应.
提高CPU的利用率.
改善程序结构,将复杂任务分为多个线程,独立运行.
多线程的缺点
对内存消耗高:线程也是程序,所以线程需要占用内存,线程越多占用内存也越多;
对CPU要求高:多线程需要协调和管理,所以需要CPU时间跟踪线程;
线程安全问题:线程之间对共享资源的访问会相互影响,必须解决竞用共享资源的问题;
单一的多线程不会出现安全问题,每个线程都在做自己的事情,没有交集。
为了解决线程安全问题,同步=排队+锁;
并发与并行并行:多个CPU同时执行多个任务。比如:多个人同时做不同的事。
并发:在一个时间段内依次执行操作.例如卖票,抢购,秒杀看似同时进行, 实际是一个一个执行
多个线程同时读写同一份共享资源时,可能会引起冲突。所以引入线程“同步”机制, 即各线程间要有先来后到;
同步就是排队+锁:几个线程之间要排队,一个个对共享资源进行操作,而不是同时进行操作;
为了保证数据在方法中被访问时的正确性,在访问时加入锁机制
同步锁可以是任何对象,必须唯一,保证多个线程获得是同一个对象(用 来充当锁标记).
同步执行过程
1.第一个线程访问,锁定同步对象,执行其中代码.
2.第二个线程访问,发现同步对象被锁定,无法访问.
3.第一个线程访问完毕,解锁同步对象.
4.第二个线程访问,发现同步对象没有锁,然后锁定并访问.
一个线程持有锁会导致其他所有需要此锁的线程挂起;在多线程竞争下, 加锁,释放锁会导致比较多的上下文切换和调度延时,引起性能问题.
案例:模拟卖票 两个窗口分别售票,票数为10张
分别使用继承Thread和实现Runnable两种方式实现
没加锁时,直接共享资源,出现了线程安全问题
确保一个时间点只有一个线程访问共享资源。可以给共享资源加一把锁,哪个 线程获取了这把锁,才有权利访问该共享资源。
使用synchronized(同步锁)关键字同步方法或代码块。
static int num = 10;//假设有10张票 static变量只有一份,多个对象共享
static Object obj = new Object();//多个线程共享的同一个锁对象
//模拟出票
@Override
public void run() {
while(true){
synchronized(obj){
if(num>0){
System.out.println(Thread.currentThread().getName()+":"+num);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
num--;
}else{
System.out.println("票卖完啦");
break;
}
}
}
}
synchronized还可以放在方法声明中,表示整个方法,为同步方法。
synchronized修饰非静态方法时,锁对象默认是this
synchronized修饰的是static方法,那么锁对象是类的Class对象.
每一个类被加载到内存时,就会为类创建一个Class类的对象,无论创建了多少个对象,Class对象只有一个
//模拟出票
@Override
public void run() {
while(true){
this.print();
if(num==0){
System.out.println("票卖完啦");
break;
}
}
}
public static synchronized void print(){
if(num>0){
System.out.println(Thread.currentThread().getName()+":"+num);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
num--;
}
}
实现Runnable
synchronized (this)此时就可以用this,这里只有一个对象
int num = 10;//这就是共享资源
@Override
public void run() {
while (true){
synchronized (this){
if(num>0){
System.out.println(Thread.currentThread().getName()+":"+num);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
num--;
}else{
System.out.println("票卖完了");
break;
}
}
}
}
public synchronized void print()就不用再定义为static
public class TicketThread2 implements Runnable{
int num = 10;//这就是共享资源
@Override
public void run() {
while(true){
this.print();
if(num==0){
System.out.println("票卖完啦");
break;
}
}
}
public synchronized void print(){
if(num>0){
System.out.println(Thread.currentThread().getName()+":"+num);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
num--;
}
}
}
synchronized关键字
synchronized加锁实现,靠底层指令控制实现
synchronized可以修饰代码块和方法,注意锁的对象(锁对象可能会变)
1-修饰代码块
synchronized (同步锁){
// 需要被同步的代码;
}
synchronized(锁对象) 锁对象可以是任何对象,但是必须是唯一的,也就是多个线程对应的是同一个锁对象.
在对象中,有一个区域对叫对象头,象头中有一个锁的标志为,无锁和已被使用
2- synchronized修饰方法
synchronized还可以放在方法声明中,表示整个方法,为同步方法。
synchronized修饰非静态方法时,锁对象默认是this
synchronized修饰的是static方法,那么锁对象是类的Class对象.
每一个类被加载到内存时,就会为类创建一个Class类的对象,无论创建了多少个对象,Class对象只有一个
synchronized加锁方式是隐式的,进入到同步代码块,自动获取锁,同步代码块执行完成后,自动释放锁
Lock(锁)从JDK 5.0开始,Java提供了更强大的线程同步机制-通过显式定义同步锁对象 来实现同步。同步锁使用Lock对象充当。
java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的 工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁, 线程开始访问共享资源之前应先获得Lock对象。
ReentrantLock类实现了Lock,它拥有与synchronized相同的并发性和内存 语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显式加 锁、释放锁.
Lock是靠java代码来控制的
Lock只能在某一段代码加锁,而且是显式的加锁和释放锁
注意:最好写在
try{
代码块
}finally{
lock.unlock();
}
里面,一旦出现异常,也保证把锁释放掉,以免出现死锁
package day16.Class;
import java.util.concurrent.locks.ReentrantLock;
public class TicketThread3 implements Runnable{
int num = 10;//这就是共享资源
ReentrantLock lock = new ReentrantLock();
//模拟出票
@Override
public void run() {
while(true){
try{
lock.lock();//加锁
if(num>0){
System.out.println(Thread.currentThread().getName()+":"+num);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
num--;
}else{
System.out.println("票卖完啦");
break;
}
}finally {
lock.unlock();//释放锁
}
}
}
}
死锁
不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃 自己需要的同步 资源,就形成了线程的死锁.
出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于 阻塞状态,无法继续.
锁嵌套时容易发生死锁现象
package day16.Class;
public class DieThread extends Thread{
boolean flag;
public DieThread(boolean flag) {
this.flag = flag;
}
static Object objA = new Object();
static Object objB = new Object();
@Override
public void run() {
if(flag){
synchronized (objA){
System.out.println("if objA");
synchronized (objB){
System.out.println("if objB");
}
}
}else{
synchronized (objB){
System.out.println("else if objB");
synchronized (objA) {
System.out.println("else if objA");
}
}
}
}
}
线程通信
线程通讯指的是多个线程通过相互牵制,相互调度,即线程间的相互作用。
涉及三个方法:
wait一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器。
notify一旦执行此方法,就会唤醒被wait的一个线程。如果有多个线程被wait, 就唤醒优先级高的那个。
notifyAll一旦执行此方法,就会唤醒所有被wait的线程。
注意: .wait(),notify(),notifyAll()三个方法必须使用在同步代码块或同步方法中。
wait()和sleep()
相同:让线程进入阻塞状态
不同:sleep()是Thread类的方法,不会释放锁,休眠时间到了后,会自动进入就绪状态
wait()是Object类的方法,会释放锁。wait后的线程,需要使用notify或notifyAll来唤醒
案例:两个线程交替打印1-100之间的数字
package day16.Class;
public class PrintThread implements Runnable{
int num = 0;
@Override
public void run() {
while (true) {
synchronized (this) {
this.notify();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (num <= 100) {
System.out.println(Thread.currentThread().getName() + ":" + num);
num++;
} else {
break;
}
try {
this.wait();//让线程等待,一定要调用的是锁对象的wait()
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
经典例题:生产者/消费者问题
生产者(Productor)将产品放在柜台(Counter),而消费者(Customer)从柜台 处取走产品,生产者一次只能生产固定数量的产品(比如:1), 这时柜台中不能 再放产品,此时生产者应停止生产等待消费者拿走产品,此时生产者唤醒消费者来 取走产品,消费者拿走产品后,唤醒生产者,消费者开始等待.
package day16.Class.PCquestion;
//生产者
public class Productor extends Thread{
Counter counter;
public Productor(Counter c) {
this.counter = c;
}
@Override
public void run() {
counter.jia();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
package day16.Class.PCquestion;
//消费者
public class Customer extends Thread{
Counter counter;
public Customer(Counter c) {
this.counter = c;
}
@Override
public void run() {
while (true){
counter.jian();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
package day16.Class.PCquestion;
//柜台
public class Counter {
int num = 1;
//生产者调用
//synchronized修饰的是非静态方法,锁对象默认是this,Counter c = new Counter();
public synchronized void jia(){
if(num == 0){
num++;
System.out.println("生产者生产了一个商品");
this.notify();
}else {
try {
this.wait();//生产者等待
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//消费者调用
public synchronized void jian(){
if(num==1){
num--;
System.out.println("消费者取走了一个商品");
this.notify();
}else {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
package day16.Class.PCquestion;
public class Test {
public static void main(String[] args) {
Counter c = new Counter();
Productor p = new Productor(c);
p.start();//启动生产线程
Customer co = new Customer(c);
co.start();//启动消费线程
}
}
新增创建线程方式Callable接口
继承Thread和实现Runnable 最终都是重写run()
run()没有返回值,也不能抛出异常,存在局限性
java推出Callable接口,里面定义了call()
实现Callable接口与使用Runnable相比,Callable功能更强大些.
相比run()方法,可以有返回值
方法可以抛出异常
支持泛型的返回值
需要借助FutureTask类,获取返回结果
接收任务
FutureTask futureTask = new FutureTask(任务);
创建线程
Thread t = new Thread(futureTask);
t.start();
Integer val = futureTask.get();获得线程call方法的返回值
package day16.Class.NewThread; import java.util.concurrent.Callable; public class SumThread implements Callable{ @Override public Integer call() throws Exception { int sum = 0; Thread.sleep(100); for (int i = 0; i <=10; i++) { sum+=i; } return sum; } }
package day16.Class.NewThread;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class Test {
public static void main(String[] args) {
SumThread sumThread = new SumThread();
FutureTask futureTask = new FutureTask(sumThread);
Thread t = new Thread(futureTask);
t.start();
try {
Integer sum = futureTask.get();
System.out.println(sum);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}



