java多线程的一点整理

2018-03-01 11:23:15来源:oschina作者:爱抓老鼠的狗人点击

分享

多线程是提高效率性能的一个重要技术,企业级开发中运用的很广泛,我在学习了一段时间之后,还是有很多地方是很懵比的,后续的会继续学习更新。


1. 多线程简单的概述


进程:正在进行的程序,比如任务管理器上的进程,QQ等等


线程:就是进程中负责执行程序的控制单元(或者执行路径),一个进程中有多个执行路径称之为多线程


开启多线程是为了同时运行多部分代码,每个线程都有自己的运行内容,这个内容称之为线程的任务。


多线程的好处:解决了多程序同时运行的问题。多线程的弊端:线程太多回到效率降低。


其实应用程序的执行是CPU在做着快速切换完成的,这个切换是随机的。

下面是多线程的几种状态



jdk 1.5之前的 多线程两种创建方式就不多说了。


1.extend Thread ,覆写run方法(run 方法就是描述的线程任务),将该类new一个对象就是创建了一个线程对象,用start()方法开启线程


2.实现Runnable接口,覆写run()方法,方法里面也是描述线程任务,new一个Thread线程对象,将任务构造的时候传递进去,用start()方法开启线程


注意:实现Runnable接口的好处


1.将线程的任务从线程的子类中分离出来,进行的单独的封装,按照面向对象的思想将任务进行了封装


2.避免了java单继承的局限性。


2.0 多线程安全问题


public class ticket implements Runnable{
privateint num=500;
public void sale(){
while (true){
synchronized(this) {
if (num>0){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"...sale=="+num--);
}else {
break;
}
} }
}@Override
public void run() {
sale();
}
}
public static void main(String[] args){//多次启动一个线程是非法的。
ticket t1= new ticket();
Thread thread1= new Thread(t1);
Thread thread2= new Thread(t1);
Thread thread3= new Thread(t1);
Thread thread4= new Thread(t1);
thread1.start();
thread2.start();
thread3.start();
thread4.start();
}

这段代码很简单,4 个线程操作同一个ticket对象,num是一个全局变量,每个线程进来休眠10ms后都减1,直到为0,可以观察到里面加了synchronized关键字(同步),如果不加synchronized,将会出现线程不安全的因素。


附:多次启动一个线程对象是非法的!


打印的结果是



分析:出现这种原因就是0线程进来后,此时num为1,判断(num>0)之后,线程休眠了,cpu释放执行权(因为cpu执行那个线程是随机切换的),1线程进来之后也出现这种情况num=1的时候,cpu释放执行权,同理其他的2个线程也发生这种情况,导致出现数据不同步的情况,要解决这种就要加锁


线程安全问题产生的原因:


1.多个线程操作共享数据


2.操作共享数据的代码有多条


用锁去解决线程安全的问题,synchronized(锁对象){需要被同步的代码},this表示当前线程任务对象


同步的好处:解决了线程的安全问题


同步的弊端:相对降低了效率,因为同步外的线程都会判断同步锁。


同步的前提:同步必须有多个线程并使用同一个锁。


同步函数的锁对象是当前固定的this,同步代码块的锁是任意的对象。


静态同步函数锁的对象是该函数所属的字节码文件对象,可以用this.getClass()或者类名.class表示


死锁的情况:同步锁的的嵌套,死锁可能会出现和谐的情况。


2.1 单例模式下多线程的安全问题


/**
* 懒汉式
* */
public static classSingle{
private staticSingle s= null;
private Single(){
}
public static Single getInstance(){

/*
* 加锁是为了解决线程的安全的问题,锁之前加个判断 是因为线程进来判断锁,比较消耗效率(解决多线程下单例的效率问题)
* */
if (s==null){
synchronized (Single.class){
//多线程访问需要同步,例如a,b两个线程,a线程执行到s==null时失去了cpu的执行权,
// 然后b线程同理执行到s==null失去cpu的执行权,这个就不是单例模式了。
//懒汉式在多线程情况下,操作s的共享数据有多条代码,因此存在线程不安全的情况,需要同步操作s共享数据的多条代码
if (s==null){
s= new Single();
}
}
} return s;
}
}

上述懒汉式在多线程情况下就不是单例模拟了。


2.3 多线程之间的通信 wait和notify机制(生成者-消费者模式)


先看一下简单的例子,2个线程,一个输入线程负责读,一个输出线程负责写


/**
* Created by ZC-16-012 on 2018/2/2.
*
* 定义多线程的资源共享数据对象
*/
public class Resource {
private Stringname;
private String sex;
private boolean flag=false;
public boolean isFlag() {
return flag;
}
public void setFlag(boolean flag) {
this.flag = flag;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
//输入线程set方法
public synchronizedvoid set(String name,String sex){
if (flag){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else {
this.name= name;
this.sex= sex;
flag=true;
this.notify();
}
}
//输出线程
public synchronized void out(){
if (!flag){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else {
System.out.println(this.getName()+"===="+this.getSex());
this.setFlag(false);
this.notify();
}
}
}public class InputTask implements Runnable{
Resource r;
public InputTask(Resource r){
this.r = r;
}
@Override
public void run() {
int x=0;
while (true){
/**
* 分析:由于资源Resource是共享数据,当输入线程获取的cpu执行权时,第一次赋值name是weiAnKang,sex是男,
* 第二次输入线程再次赋值name 是'丽丽'时,由于线程之间是cpu执行权的切换,此时输出线程获取cpu的执行权,
* 输出线程的结果就是'丽丽====男',这样导致多线程操作同一个资源时出现的安全问题
* */
//todo:此时在输入线程加上同步锁依然没有解决,Resource共享数据同步的问题,出现这种问题应该考虑多个线程之间是不是判断同一个锁
if (x==0){
r.set("weiAnKang","nan");
}else {
r.set("丽丽","女女女");
}
x=(x+1)%2;
}
}
}
/**
* Created by ZC-16-012 on 2018/2/2.
*
* 定义输出线程的任务
*/
public class OutputTask implements Runnable{
Resource r;
public OutputTask(Resource r){
this.r=r;
}
@Override
public void run() {
while (true){
r.out();
}
}
}
/**
* Created by ZC-16-012 on 2018/2/2.
*
* 线程之间的通信
*
* 等待/唤醒机制
* wait(): 让线程处于冻结状态,被wait的线程会被存储到线程池中
* notify():唤醒线程池中的一个线程(任意的)
* notifyAll():唤醒线程池中的所有线程
* 这些方法必须定义在同步中,因为这些方法是用于操作线程的状态的方法,
* 必须要明确到底操作的是那个锁上的线程
*
*/
public class ThreadCommunicationDemo {
public static void main(String[] args){
Resource r= new Resource();
InputTask input= new InputTask(r);
OutputTask output= new OutputTask(r);
//创建线程,并将线程的任务传递给线程对象
Thread t1= new Thread(input);
Thread t2= new Thread(output);
//开启线程
t1.start();
t2.start();
}
}

等待/唤醒机制 * wait(): 让线程处于冻结状态,被wait的线程会被存储到线程池中 * notify():唤醒线程池中的一个线程(任意的) * notifyAll():唤醒线程池中的所有线程 * 这些方法必须定义在同步中,因为这些方法是用于操作线程的状态的方法, * 必须要明确到底操作的是那个锁上的线程


2.4 jdk1.5之后的java.util.concurrent.locks.Lock的应用


假如有多个线程进行读的操作,多个线程进行写的操作呢


/**
* Created by ZC-16-012 on 2018/2/5.
*
* 生产者和消费者
* while判断标记,解决了线程获取cpu执行权后,是否要执行
* notifyAll 解决了本方线程一定会唤醒对方线程
*/
public class ResourceClass {
private String name;
private intcount=1;
private boolean flag=false;
private Lock lock= new ReentrantLock();//使用jdk1.5版本之后的锁对象
//通过已有的锁对象获取该锁上的监视器对象,创建两组监视器,一组监视生产者,一组监视消费者
Condition producerCon= lock.newCondition();
Condition consumerCon= lock.newCondition();
/**
* 分析:0,1 是生产者线程,2 3是消费者线程,假设0先获取cpu的执行权,2线程消费,有一种情况当0,1线程
*t0(活),notify之后正好是 唤醒t1 线程,t1判断flag=true,t0,t1一直在while循环中等待,出现了死锁情况
*出现这种情况就是生成者线程唤醒的是另一个生成者线程,应该生成者要保证唤醒的至少有消费者线程
* */
/**
* 对于synchronized 修饰的同步代码块,锁是隐式的
* 可以使用interrupt()方法将线程从冻结状态强制恢复到运行状态中,让线程具有cpu的执行资格
* 该强制动作会发生 InterruptedException 异常
*
* */
publicvoid set(String name) {
lock.lock();
try {
while (flag) {
try {
//this.wait();// t0(活),notify 正好唤醒t1
//通过condition的await 让线程冻结
producerCon.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.name = name + count;
count++;
System.out.println(Thread.currentThread().getName() + "..生产者.." + this.name);
flag = true;
// notifyAll();//唤醒了所有线程池中等待的线程
//通过condition的signalAll 唤醒消费者的所有线程
consumerCon.signal();
} finally {
lock.unlock();//释放锁
}
}
publicvoid out (){
lock.lock();
try {
if (!flag) {
try {
//this.wait();
consumerCon.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
System.out.println(Thread.currentThread().getName() + ".......消费者.." + this.name);
flag = false;
//notifyAll();
//消费者唤醒所有生成者的线程
producerCon.signal();
}
} finally {
lock.unlock();
}
}
}
public class Producer implements Runnable{
ResourceClass r;
public Producer(ResourceClass r){
this.r=r;
}
@Override
public void run() {
while (true){
r.set("馒头");
}
}
}
public class Consumer implements Runnable{
ResourceClass r;
public Consumer(ResourceClass r){
this.r = r;
}
@Override
public void run() {
while (true){
r.out();
}
}
}
/**
* Created by ZC-16-012 on 2018/2/5.
*/
public class ProducerAndConsumer {
/**
*Thread.currentThread().toString() 会打印线程的名称,优先级,线程组
*线程的优先级 MAX_PRIORITY 10,MIN_PRIORITY 1, NORM_PRIORITY 5,数字越高,表示优先级越大,
*cpu执行该线程的概率也就越大,例如:t0.setPriority(Thread.MAX_PRIORITY); 将t0线程的优先级设置成最大
*
* */
public staticvoidmain(String[] args){
ResourceClass r= new ResourceClass();
Producer pro= new Producer(r);
Consumer con= new Consumer(r);
Thread t0 = new Thread(pro);
Thread t1 = new Thread(pro);
Thread t2 = new Thread(con);
Thread t3 = new Thread(con);
t0.start();
/**
* try {
t0.join(); //t0线程要申请加入进来,运行。让这个临时线程先执行完
} catch (InterruptedException e) {
e.printStackTrace();
}
* */
//t0.setPriority(Thread.MAX_PRIORITY);
t1.start();
t2.start();
/**
*t2.setDaemon(true) 设置成守护线程(后台线程),前台线程其他线程结束,这个线程也结束
* */
t3.start();}
}

以上例子便是多生产者多消费者的例子


3.0 volatile关键字


很多时候不会区分volatile和synchronized,先看一下这个例子


public class RunThread extends Thread{
privateboolean flag = true;
publicvoidsetFlag(boolean flag){
this.flag=flag;
}
@Override
publicvoid run() {
while (flag){
}
System.out.println(Thread.currentThread().getName() + "...线程运行结束");
}
/**
* 分析:main方法中创建了t0线程,休眠3s后,将t线程中的全局变量设置了false,主线程结束后,却没有打印t0线程中的 "...线程运行结束"
* 说明t0线程一直while循环中运行,对于t0线程而言,flag的值并没有改变
*
* 总结:在java中,每一个线程都有一个内存区,其中存放着所有线程共享的主内存中的变量值的拷贝,当线程执行时,他在自己的工作
* 内存区中操作这些变量,为了存取一个共享的变量,一个线程通常先获取锁定并去清楚它的内存工作区,把这些变量从所有线程的共享内存区中
* 正确的装入到他自己所在的工作内存区中,当线程解锁时保证该工作内存区中变量的值写回到共享内存中
*
* volatile 关键字强制线程到主内存中(共享内存中)去读取变量,而不是去线程工作内存区中读取,从而实现了多个线程见的变量可见
* */
public static void main(String[] args) throws InterruptedException {
RunThread t0= new RunThread();
t0.start();
Thread.sleep(3000);
t0.setFlag(false);
System.out.println("flag的值已经被设置成了false。。");
Thread.sleep(1000);
System.out.println(t0.flag);
}
}

网上也搜了很多博客,https://www.cnblogs.com/yzwall/p/6661528.html 这遍博客说的很清楚


3.0Atomic原子类


/**
* Created by ZC-16-012 on 2018/2/8.
*/
public class VolatileNoAtomic extends Thread{
@Override
public void run() {
addCount();
}
/**
* volatile关键字具有可见性,并不具备原子性
* Atomic原子类 可以保证结果是原子性,并不能保证方法本身是原子性
* */
//private volatile staticint count;
private staticAtomicInteger count =new AtomicInteger(0);
private static void addCount(){
for (int i=0;i<1000;i++){
//count++;
count.incrementAndGet();
}
System.out.println(count);}
public static void main(String[] args){
VolatileNoAtomic[] arr= new VolatileNoAtomic[10];
for (int i=0;i<10;i++){
arr[i]=new VolatileNoAtomic();
}
for (int i=0;i<10;i++){
arr[i].start();
}
}
}

可以通过这个例子看一下Atomic对象和volatile关键字的一些区别


3.1 网上有一个联系题有4个线程分别获取C D E F 盘的大小,第5个线程统计总大小


public class ContactThread {
public static void main(String[] args){
/**
* CountDownLatch 是一个同步倒数计数器,构造时传入int参数,每调用一次countDown()方法
* 计数器减1
* */
final CountDownLatch countDownLatch= new CountDownLatch(4);
ExecutorService executorService= Executors.newFixedThreadPool(6);
final DiskMemory diskMemory= new DiskMemory();
for (int i=0;i<4;i++)
executorService.execute(new Runnable() {
@Override
public void run() {
int timer = new Random().nextInt(5);
try {
Thread.sleep(timer * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
int diskSize = diskMemory.getSize();
System.out.printf("完成磁盘的统计任务,耗时%d秒,磁盘大小为%d./n", timer, diskSize);
//统计每个磁盘的大小
diskMemory.setSize(diskSize);
//任务完成后,计数器减一
countDownLatch.countDown();
System.out.println("count num=" + countDownLatch.getCount());
}
});
//主线程一直被阻塞,直到所有的线程统计完
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.printf("全部线程统计完,所有磁盘总大小./n" + ",totalSize = " + diskMemory.getTotalSize());
executorService.shutdown();
}
}
/**
* Created by ZC-16-012 on 2018/2/27.
*
* 定义磁盘对象
*/
public class DiskMemory {
private int totalSize;
public int getSize(){
return (new Random().nextInt(3)+1)*100;
}
public void setSize(int size){
totalSize+=size;
}
public int getTotalSize(){
return totalSize;
}
}

这边用到了java.util.concurrent.CountDownLatch的一个同步倒数计数器。


当然java并发编程是一个很复杂的技术,远远不止于这些,后面学习到会陆续更新,比如java线程池的运用。


最新文章

123

最新摄影

闪念基因

微信扫一扫

第七城市微信公众平台