一、一些概念
1、线程同步
同一个进程的多个线程共享一块存储空间,在带来方便的同时,也带来了访问冲突问题,为了保证数载方法中被访问时的正确性,在访问时加入了锁机制synchronized,当一个线程获得对象的排它锁,独占资源,其他线程必须等待,使用后释放锁即可。
一个线程持有锁会导致其他所有需要此锁的线程挂起;
在多线程竞争下,加锁,释放锁会导致比较多的上下文切换和调度延时,引起性能问题;
如果一个优先级高的线程等待一个优先级低的线程释放锁会导致优先级倒置,引起性能问题
2、死锁
多个线程各自占有一些共享资源,并且相互等待其他线程占有的资源才能运行,而导致两个或者多个线程都在等待对方释放资源,都停止执行的情况。某个同步块同时拥有“两个以上对象的锁”时,就可能会发生死锁的问题。
下列是死锁情况。解决办法:嵌套的synchronized拆出来。
public class CopyOnWriteArrayListTest {
public static void main(String[] args) throws InterruptedException {
UseObjec useObjec1=new UseObjec(false);
UseObjec useObjec2=new UseObjec(true);
useObjec1.start();
useObjec2.start();
}
}
class ObjOne{
}
class ObjTwo{
}
class UseObjec extends Thread{
static ObjOne objOne=new ObjOne();
static ObjTwo objTwo=new ObjTwo();
private Boolean flag;
UseObjec(){
}
UseObjec(Boolean flag){
this.flag=flag;
}
@Override
public void run(){
useMethod();
}
private void useMethod(){
if(flag==true){
synchronized (objOne){
System.out.println(Thread.currentThread().getName()+"使用对象1");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (objTwo){
System.out.println(Thread.currentThread().getName()+"使用对象2");
}
}
}else {
synchronized (objTwo){
System.out.println(Thread.currentThread().getName()+"使用对象2");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (objOne){
System.out.println(Thread.currentThread().getName()+"使用对象1");
}
}
}
}
}
3、Lock(锁)
Java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应该先获得Lock对象。
ReetranLock类实现了Lock,它拥有与synchronized相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显式加锁、释放锁。
import java.util.concurrent.locks.ReentrantLock;
public class CallabaleTest {
public static void main(String[] args) {
ReentrantLockTest reentrantLockTest=new ReentrantLockTest();
new Thread(reentrantLockTest).start();
new Thread(reentrantLockTest).start();
new Thread(reentrantLockTest).start();
}
}
class ReentrantLockTest implements Runnable{
static Integer tick=10;
private final ReentrantLock reentrantLock=new ReentrantLock();
@Override
public void run() {
try{
//上锁
reentrantLock.lock();
while (true){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(tick>0){
System.out.println(tick--);
}else {
break;
}
}
}finally {
//释放锁
reentrantLock.unlock();
}
}
}
二、线程通信
线程同步问题:生产者和消费者共享一个资源,并且生产者和消费者之间相互依赖,互为条件:
- 对于生产者,没有生产产品之前,要通知消费者等待。而生产了产品之后,又需要马上通知消费者消费。
- 对于消费者,在消费之后,要通知生产者已经结束消费,需要生产新的产品以供消费。
- 在生产者消费者问题中, 仅有synchronized是不够的。
synchronized可阻止并发更新同一个共享资源,实现了同步
synchronized不能用来实现不同线程之间的消息传递
Java提供解决线程之间通信的方法:
1、管程法(生产者/消费者模式)
生产者:负责生产数据的模块;
消费者:负责处理数据的模块;
缓冲区:消费者不能直接使用生产者的数据,在他们之间有个缓冲区。
生产者将生产好的数据放入缓冲区,消费者从缓冲区拿出数据。
public class PipelineTest {
public static void main(String[] args) {
BufferZone bufferZone=new BufferZone();
new Produce(bufferZone).start();
new Consumer(bufferZone).start();
}
}
//生产者
class Produce extends Thread{
private BufferZone bufferZone;
public Produce(BufferZone bufferZone){
this.bufferZone=bufferZone;
}
@Override
public void run(){
for (int i = 0; i <100; i++) {
System.out.println("造一个,id:"+i);
bufferZone.push(new Producers(i));
}
}
}
//消费者
class Consumer extends Thread{
private BufferZone bufferZone;
public Consumer(BufferZone bufferZone){
this.bufferZone=bufferZone;
}
@Override
public void run(){
for (int i = 0; i < 100; i++) {
Producers use = bufferZone.use();
System.out.println("用了一个,id:"+use.getId());
}
}
}
//产品
class Producers{
private Integer id;
public Producers(Integer id){
this.id=id;
}
public Integer getId() {
return id;
}
}
//缓冲区
class BufferZone{
//容器
Producers[] producers=new Producers[10];
//容器大小
int size=0;
//生产产品
public synchronized void push(Producers producer){
if(size==producers.length){
//缓存区满了,停止生产
}else {
producers[size]=producer;
size++;
}
}
//消费产品
public synchronized Producers use(){
//缓冲区有东西,消费
if(size==0){
}
size--;
return producers[size];
}
}
2、信号灯法
角色:生产者、消费者、产品
主要使用方法:wait()、notifyAll()
public class PipelineTest {
public static void main(String[] args) {
Producers bufferZone=new Producers();
new Produce(bufferZone).start();
new Consumer(bufferZone).start();
}
}
//生产者
class Produce extends Thread{
private Producers bufferZone;
public Produce(Producers bufferZone){
this.bufferZone=bufferZone;
}
@Override
public void run(){
for (int i = 0; i <10; i++) {
bufferZone.play("节目"+i);
}
}
}
//消费者
class Consumer extends Thread{
private Producers bufferZone;
public Consumer(Producers bufferZone){
this.bufferZone=bufferZone;
}
@Override
public void run(){
for (int i = 0; i < 10; i++) {
bufferZone.watch();
}
}
}
//产品
class Producers{
private String name;
private Boolean flag=true;
//展示
public synchronized void play(String name){
//如果观众在观看就等待
if(!flag){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.name=name;
System.out.println("表演"+name);
//通知观看
this.notifyAll();//唤醒
//转置信号
this.flag=!this.flag;
}
//观看
public synchronized void watch(){
//如果没有界面就等待
if(flag){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("展示:::"+name);
//通知表演
this.notifyAll();
//转置信号
this.flag=!this.flag;
}
}
3、线程池
背景:经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大。
思路:提前创建多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建
销毁、实现重复利用。
好处:提高相应速度
降低资源消耗
便于线程管理:corePoolSize:核心池的大小
maximumPoolSize:最大线程数
keepAliveTime:线程没有任务时最多保持多长时间后会终止
Java提供的线程池相关类:ExecutorService、Executors
ExecutorService:线程池接口。
void execute(Runnable command):执行任务/命令,没有返回值。
<T> Future<T> submit(Callable<T> task):执行任务,有返回值。
void shutdown():关闭连接池
Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class PoolTest {
public static void main(String[] args) {
ExecutorService service= Executors.newFixedThreadPool(3);
service.execute(new PoolRunnableTest());
service.execute(new PoolRunnableTest());
service.execute(new PoolRunnableTest());
service.execute(new PoolRunnableTest());
service.execute(new PoolRunnableTest());
service.shutdown();
}
}
class PoolRunnableTest implements Runnable{
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"666");
}
}
二、案例
1、CopyOnWriteArrayList(安全的集合)
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
public class CopyOnWriteArrayListTest {
public static void main(String[] args) throws InterruptedException {
//list不安全
List<String> list=new ArrayList<>();
for (int i = 0; i < 1000; i++) {
new Thread(()->{
list.add("");
}).start();
}
//list很大几率不会等于1000
System.out.println(list.size());
//安全的集合
CopyOnWriteArrayList<String> listForSafe=new CopyOnWriteArrayList<>();
for (int i = 0; i < 1000; i++) {
new Thread(()->{
listForSafe.add("");
}).start();
}
//此处打印可能不到1000,因为主线程跑完,子线程还未执行完毕
//listForSafe等于1000
System.out.println(listForSafe.size());
}
}