java线程

Wine7.22实战:如何在Ubuntu上完美运行通达信和微信(附字体配置技巧) 本文详细介绍了如何在Ubuntu系统上使用Wine7.22版本,完美运行通达信和微信这两款Windows专属软件。通过选择稳定的Wine7.22版本、利用现成的兼容容器,并重点解决了中文字体渲染的核心难题,提供了从环境搭建、字体配置到软件安装调优的一站式实战指南,帮助用户在Linux环境下无缝使用关键金融与通讯工具。 阅读详情
一、简介
描述
进程(process):是一块包含了某些资源的内存区域。操作系统利用进程把它的工作划分为一些功能单元。
线程(thread):进程中所包含的一个或多个执行单元称为线程(thread)。进程还拥有一个私有的虚拟地址空间,该空间仅能被它所包含的线程访问。可以看成是轻量级的进程,是CPU调度和分派的基本单位。
区别
1、调度 :从上面的定义可以看出一个是调度和分派的基本单位,一个是拥有资源的基本单位
2、共享地址空间,资源:进程拥有各自独立的地址空间,资源,所以共享复杂,需要用IPC,同步简单; 线程共享所属进程的资源,共享简单,但同步复杂,要通过加锁等措施。
3、占用内存,cpu: 进程占用内存多,切换复杂,CPU利用率低; 线程占用内存少,切换简单,CPU利用率高。
4、相互影响: 进程间不会相互影响; 一个线程挂掉会导致整个进程挂掉。
二、线程的生命周期及五种基本状态
关于Java中线程的生命周期图


Java线程具有五中基本状态
新建状态(New):当线程对象对创建后,即进入了新建状态,如:Thread t = new MyThread();
就绪状态(Runnable):当调用线程对象的start()方法(t.start();),线程即进入就绪状态。处于就绪状态的线程,只是说明此线程已经做好了准备,随时等待CPU调度执行,并不是说执行了t.start()此线程立即就会执行;
运行状态(Running):当CPU开始调度处于就绪状态的线程时,此时线程才得以真正执行,即进入到运行状态。注:就绪状态是进入到运行状态的唯一入口,也就是说,线程要想进入运行状态执行,首先必须处于就绪状态中;
阻塞状态(Blocked):处于运行状态中的线程由于某种原因,暂时放弃对CPU的使用权,停止执行,此时进入阻塞状态,直到其进入到就绪状态,才有机会再次被CPU调用以进入到运行状态。根据阻塞产生的原因不同,阻塞状态又可以分为三种:
1.等待阻塞:运行状态中的线程执行wait()方法,使本线程进入到等待阻塞状态;
2.同步阻塞:线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态;
3.其他阻塞:通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
三、Java多线程的创建及启动
Java中线程的创建常见有如三种基本形式
1.继承Thread类,重写该类的run()方法。
class MyThread extends Thread {
     private int i = 0;
     public void run() {
        for (i = 0; i < 100; i++) {
             System.out.println(Thread.currentThread().getName() + " " + i);
         }
     }
}

public class ThreadTest {
     public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + " " + i);
            if (i == 30) {
                Thread myThread1 = new MyThread();     // 创建一个新的线程  myThread1  此线程进入新建状态
                Thread myThread2 = new MyThread();     // 创建一个新的线程 myThread2 此线程进入新建状态
                myThread1.start();                     // 调用start()方法使得线程进入就绪状态
                myThread2.start();                     // 调用start()方法使得线程进入就绪状态
            }
        }
    }
}
如上所示,继承Thread类,通过重写run()方法定义了一个新的线程类MyThread,其中run()方法的方法体代表了线程需要完成的任务,称之为线程执行体。当创建此线程类对象时一个新的线程得以创建,并进入到线程新建状态。通过调用线程对象引用的start()方法,使得该线程进入到就绪状态,此时此线程并不一定会马上得以执行,这取决于CPU调度时机。
2.实现Runnable接口,并重写该接口的run()方法,该run()方法同样是线程执行体,创建Runnable实现类的实例,并以此实例作为Thread类的target来创建Thread对象,该Thread对象才是真正的线程对象。
class MyRunnable implements Runnable {
    private int i = 0;
    public void run() {
        for (i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + " " + i);
        }
    }
}
public class ThreadTest {
    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
相信以上两种创建新线程的方式大家都很熟悉了,那么Thread和Runnable之间到底是什么关系呢?我们首先来看一下下面这个例子。
public class ThreadTest {
    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + " " + i);
            if (i == 30) {
                Runnable myRunnable = new MyRunnable();
                Thread thread = new MyThread(myRunnable);
                   thread.start();
            }
        }
    }
}

class MyRunnable implements Runnable {
    private int i = 0;
    public void run() {
        System.out.println("in MyRunnable run");
        for (i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + " " + i);
        }
    }
}

class MyThread extends Thread {
    private int i = 0;
    public MyThread(Runnable runnable){
        super(runnable);
    }

    public void run() {
        System.out.println("in MyThread run");
        for (i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + " " + i);
        }
    }
}
同样的,与实现Runnable接口创建线程方式相似,不同的地方在于
Thread thread = new MyThread(myRunnable);
那么这种方式可以顺利创建出一个新的线程么?答案是肯定的。至于此时的线程执行体到底是MyRunnable接口中的run()方法还是MyThread类中的run()方法呢?通过输出我们知道线程执行体是MyThread类中的run()方法。其实原因很简单,因为Thread类本身也是实现了Runnable接口,而run()方法最先是在Runnable接口中定义的方法。
public interface Runnable {    public abstract void run();}
我们看一下Thread类中对Runnable接口中run()方法的实现:
public void run() {    if (target != null) {        target.run();    }}
也就是说,当执行到Thread类中的run()方法时,会首先判断target是否存在,存在则执行target中的run()方法,也就是实现了Runnable接口并重写了run()方法的类中的run()方法。但是上述给到的列子中,由于多态的存在,根本就没有执行到Thread类中的run()方法,而是直接先执行了运行时类型即MyThread类中的run()方法。
----------------------------------------------------------------
实现Runnable接口相比继承Thread类有如下好处:
  • 避免点继承的局限,一个类可以继承多个接口。
  • 适合于资源的共享
-----------------------------------------------------------------

3.使用Callable和Future接口创建线程。具体是创建Callable接口的实现类,并实现clall()方法。并使用FutureTask类来包装Callable实现类的对象,且以此FutureTask对象作为Thread对象的target来创建线程。
 看着好像有点复杂,直接来看一个例子就清晰了。
public class ThreadTest {
    public static void main(String[] args) {
        Callable<Integer> myCallable = new MyCallable();    // 创建MyCallable对象
        FutureTask<Integer> ft = new FutureTask<Integer>(myCallable); //使用FutureTask来包装MyCallable对象
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + " " + i);
            if (i == 30) {
                Thread thread = new Thread(ft);   //FutureTask对象作为Thread对象的target创建新的线程
                thread.start();                      //线程进入到就绪状态
            }
        }
        System.out.println("主线程for循环执行完毕..");
        try {
            int sum = ft.get();            //取得新创建的新线程中的call()方法返回的结果
            System.out.println("sum = " + sum);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}

class MyCallable implements Callable<Integer> {
    private int i = 0;
    // 与run()方法不同的是,call()方法具有返回值
    public Integer call() {
        int sum = 0;
        for (; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + " " + i);
            sum += i;
        }
       return sum;
    }
}
首先,我们发现,在实现Callable接口中,此时不再是run()方法了,而是call()方法,此call()方法作为线程执行体,同时还具有返回值!在创建新的线程时,是通过FutureTask来包装MyCallable对象,同时作为了Thread对象的target。那么看下FutureTask类的定义:
public class FutureTask<V> implements RunnableFuture<V> {    //....}
public interface RunnableFuture<V> extends Runnable, Future<V> {    void run();}
于是,我们发现FutureTask类实际上是同时实现了Runnable和Future接口,由此才使得其具有Future和Runnable双重特性。通过Runnable特性,可以作为Thread对象的target,而Future特性,使得其可以取得新创建线程中的call()方法的返回值。
执行下此程序,我们发现sum = 4950永远都是最后输出的。而“主线程for循环执行完毕..”则很可能是在子线程循环中间输出。由CPU的线程调度机制,我们知道,“主线程for循环执行完毕..”的输出时机是没有任何问题的,那么为什么sum =4950会永远最后输出呢?
原因在于通过ft.get()方法获取子线程call()方法的返回值时,当子线程此方法还未执行完毕,ft.get()方法会一直阻塞,直到call()方法执行完毕才能取到返回值。
上述主要讲解了三种常见的线程创建方式,对于线程的启动而言,都是调用线程对象的start()方法,需要特别注意的是:不能对同一线程对象两次调用start()方法。
四、 Java多线程的就绪、运行和死亡状态
就绪状态转换为运行状态:当此线程得到处理器资源;
运行状态转换为就绪状态:当此线程主动调用yield()方法或在运行过程中失去处理器资源。
运行状态转换为死亡状态:当此线程线程执行体执行完毕或发生了异常。
此处需要特别注意的是:当调用线程的yield()方法时,线程从运行状态转换为就绪状态,但接下来CPU调度就绪状态中的哪个线程具有一定的随机性,因此,可能会出现A线程调用了yield()方法后,接下来CPU仍然调度了A线程的情况。
由于实际的业务需要,常常会遇到需要在特定时机终止某一线程的运行,使其进入到死亡状态。目前最通用的做法是设置一boolean型的变量,当条件满足时,使线程执行体快速执行完毕。如:
public class ThreadTest {
    public static void main(String[] args) {
        MyRunnable myRunnable = new MyRunnable();
        Thread thread = new Thread(myRunnable);
        
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + " " + i);
            if (i == 30) {
                thread.start();
            }
            if(i == 40){
                myRunnable.stopThread();
            }
        }
    }
}

class MyRunnable implements Runnable {
    private boolean stop;
    public void run() {
        for (int i = 0; i < 100 && !stop; i++) {
            System.out.println(Thread.currentThread().getName() + " " + i);
        }
    }
    public void stopThread() {
        this.stop = true;
    }
}

五、线程的优先级和让步
线程的让步是通过Thread.yield()来实现,暂停当前正在执行的线程对象,并执行其他线程。线程存在优先级,优先级范围在1~10,JVM线程调度程序是基于优先级的抢先调度机制。
设置线程的优先级:线程默认的优先级是创建它的执行线程的优先级,可以通过setPriority(int newPriority)更改优先级。
六、线程的同步与锁
线程的同步是为了防止多个线程访问一个数据对象时,对数据造成的破坏。当程序运行到非静态的synchronized同步方法上时,自动获得与正在执行代码类的当前实例(this实例)有关的锁。获得一个对象的锁也称为获取锁、锁定对象、在对象上锁定或在对象上同步。
一个对象只有一个锁。所以,如果一个线程获得该锁,就没有其他线程可以获得锁,直到第一个线程释放(或返回)锁。这也意味着任何其他线程都不能进入该对象上的synchronized方法或代码块,直到该锁被释放。释放锁是指持锁线程退出了synchronized同步方法或代码块。
关于锁和同步,有以下几个要点:
1、只能同步方法,而不能同步变量和类;
2、每个对象只有一个锁;当提到同步时,应该清楚在什么上同步?也就是说,在哪个对象上同步?
3、不必同步类中所有的方法,类可以同时拥有同步和非同步方法。
4、如果两个线程要执行一个类中的synchronized方法,并且两个线程使用相同的实例来调用方法,那么一次只能有一个线程能够执行方法,另一个需要等待,直到锁被释放。也就是说:如果一个线程在对象上获得一个锁,就没有任何其他线程可以进入(该对象的)类中的任何一个同步方法。
5、如果线程拥有同步和非同步方法,则非同步方法可以被多个线程自由访问而不受锁的限制。
6、线程睡眠时,它所持的任何锁都不会释放。
7、线程可以获得多个锁。比如,在一个对象的同步方法里面调用另外一个对象的同步方法,则获取了两个对象的同步锁。
8、同步损害并发性,应该尽可能缩小同步范围。同步不但可以同步整个方法,还可以同步方法中一部分代码块。
9、调用同一个对象中非静态同步方法的线程将彼此阻塞。如果是不同对象,则每个线程有自己的对象的锁,线程间彼此互不干预。
10、调用同一个类中的静态同步方法的线程将彼此阻塞,它们都是锁定在相同的Class对象上。
11、静态同步方法和非静态同步方法将永远不会彼此阻塞,因为静态方法锁定在Class对象上,非静态方法锁定在该类的对象上。
七、线程的交互
void notify():唤醒在此对象监视器上等待的单个线程。
void notifyAll():唤醒在此对象监视器上等待的所有线程。
void wait():导致当前的线程等待,直到其他线程调用此对象的notify()方法或notifyAll()方法。
wait()还有另外两个重载方法:
void wait(long timeout):导致当前的线程等待,直到其他线程调用此对象的notify()方法或notifyAll()方法,或者超过制定的时间量。
void wait(long timeout, int nanos):导致当前的线程等待,直到其他线程调用此对象的notify()方法或notifyAll()方法,或者其他某个线程中断当前线程,或者已超过某个实际时间量。
以下为生产者-消费者-仓库模型:
public class Test {
	public static void main(String[] args) {
		Godown godown = new Godown(30);
		Consumer c1 = new Consumer(50, godown);
		Consumer c2 = new Consumer(20, godown);
		Consumer c3 = new Consumer(10, godown);
		Producer p1 = new Producer(10, godown);
		Producer p2 = new Producer(10, godown);
		Producer p3 = new Producer(10, godown);
		Producer p4 = new Producer(10, godown);
		Producer p5 = new Producer(10, godown);
		Producer p6 = new Producer(10, godown);
		Producer p7 = new Producer(80, godown);
		
		c1.start();
		c2.start();
		c3.start();
		p1.start();
		p2.start();
		p3.start();
		p4.start();
		p5.start();
		p6.start();
		p7.start();
	}
}

/**
 * @author dong
 *
 */
class Godown {
	public static final int MAX_SIZE = 100;// 最大库存量
	public int curnum;// 当前库存量

	Godown() {

	}

	Godown(int curnum) {
		this.curnum = curnum;
	}

	/**
	 * 生产指定数量的产品
	 * 
	 * @param neednum
	 */
	public synchronized void produce(int neednum) {
		// 测试是否需要产品
		while (neednum + curnum > MAX_SIZE) {
			System.out.println("要生产的产品数量为:" + neednum + "超过剩余库存量:" + (MAX_SIZE - curnum) + ",暂时不能执行生产任务!");
			try {
				// 当前的生产线程等待
				wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		// 满足生产条件,则进行生产,这里简单的更改当前库存量
		curnum += neednum;
		System.out.println("已经生产了" + neednum + "个产品,现库存量为:" + curnum);
		// 唤醒在此对象监视器上的等待的所有线程
		notifyAll();
	}

	/**
	 * 消费制定数量的产品
	 * 
	 * @param neednum
	 */
	public synchronized void consume(int neednum) {
		// 测试是否可以消费
		while (curnum < neednum) {
			try {
				// 当前的生产线程等待
				wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		// 满足消费条件,则进行消费,这里简单的更改当前库存量
		curnum -= neednum;
		System.out.println("已经消费了:" + neednum + "个产品,现仓库储量为:" + curnum);
		// 唤醒再次对象监视器上等待的所有线程
		notifyAll();
	}

}

/**
 * 生产者
 * 
 * @author dong
 *
 */
class Producer extends Thread {
	private int neednum;// 生产产品的数量
	private Godown godown;// 仓库

	Producer(int neednum, Godown godown) {
		this.neednum = neednum;
		this.godown = godown;
	}

	public void run() {
		// 生产指定数量的产品
		godown.produce(neednum);
	}
}

/**
 * 消费者
 * 
 * @author dong
 */
class Consumer extends Thread {
	private int neednum;//生产产品的数量
	private Godown godown;//仓库
	
	Consumer(int neednum, Godown godown) {
		this.neednum = neednum;
		this.godown = godown;
	}
	
	public void run() {
		//消费制定数量的产品
		godown.consume(neednum);
	}
}
八、线程的调度-合并
join为非静态方法,定义如下:
void join() //等待该线程终止。
void join(long millis) //等待该线程终止的时间最长为 millis毫秒。
void join(long millis,int nanos) //等待该线程终止的时间最长为 millis毫秒 + nanos 纳秒。
九、线程的调度-守护线程
守护线程与普通线程写法上基本么啥区别,调用线程对象的方法setDaemon(true),则可以将其设置为守护线程。
守护线程使用的情况较少,但并非无用,举例来说,JVM的垃圾回收、内存管理等线程都是守护线程。还有就是在做数据库应用时候,使用的数据库连接池,连接池本身也包含着很多后台线程,监控连接个数、超时时间、状态等等。
setDaemon方法的详细说明:public final void setDaemon(boolean on)将该线程标记为守护线程或用户线程。当正在运行的线程都是守护线程时,Java虚拟机退出。
该方法必须在启动线程前调用。
该方法首先调用该线程的 checkAccess方法,且不带任何参数。这可能抛出 SecurityException(在当前线程中)。
参数:
on - 如果为true,则将该线程标记为守护线程。
抛出:
IllegalThreadStateException - 如果该线程处于活动状态。
SecurityException - 如果当前线程无法修改该线程。
另请参见:
isDaemon(), checkAccess()
十、volatile关键字
1、volatile关键字的两层语义
一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,那么就具备了两层语义:
1)保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。
2)禁止进行指令重排序。
//线程1
boolean stop = false;
while(!stop){
    doSomething();
}
 
//线程2
stop = true;
这段代码是很典型的一段代码,很多人在中断线程时可能都会采用这种标记办法。但是事实上,这段代码会完全运行正确么?即一定会将线程中断么?不一定,也许在大多数时候,这个代码能够把线程中断,但是也有可能会导致无法中断线程(虽然这个可能性很小,但是只要一旦发生这种情况就会造成死循环了)。
下面解释一下这段代码为何有可能导致无法中断线程。在前面已经解释过,每个线程在运行过程中都有自己的工作内存,那么线程1在运行的时候,会将stop变量的值拷贝一份放在自己的工作内存当中。
那么当线程2更改了stop变量的值之后,但是还没来得及写入主存当中,线程2转去做其他事情了,那么线程1由于不知道线程2对stop变量的更改,因此还会一直循环下去。但是用volatile修饰之后就变得不一样了:
第一:使用volatile关键字会强制将修改的值立即写入主存;
第二:使用volatile关键字的话,当线程2进行修改时,会导致线程1的工作内存中缓存变量stop的缓存行无效;
第三:由于线程1的工作内存中缓存变量stop的缓存行无效,所以线程1再次读取变量stop的值时会去主存读取。
那么在线程2修改stop值时(当然这里包括2个操作,修改线程2工作内存中的值,然后将修改后的值写入内存),会使得线程1的工作内存中缓存变量stop的缓存行无效,然后线程1读取时,发现自己的缓存行无效,它会等待缓存行对应的主存地址被更新之后,然后去对应的主存读取最新的值。那么线程1读取到的就是最新的正确的值。
十一、线程池
线程池的基本思想还是一种对象池的思想,开辟一块内存空间,里面存放了众多(未死亡)的线程,池中线程执行调度由池管理器来处理。当有线程任务时,从池中取一个,执行完成后线程对象归池,这样可以避免反复创建线程对象所带来的性能开销,节省了系统的资源。
1、固定大小的线程池
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
 * @param :Java线程:线程池
 * @author dong 2017-07-10
 */
public class MyThread extends Thread{
    public void run(){
        System.out.println(Thread.currentThread().getName() + "正在执行。。。");
    }
}

class Test{
    public static void main(String[] args) {
        //创建一个可重用固定线程数的线程池
        ExecutorService pool = Executors.newFixedThreadPool(2);
        //创建实现了Runnable接口对象,Thread对象当然也实现可Runnable接口
        Thread t1 = new MyThread();
        Thread t2 = new MyThread();
        Thread t3 = new MyThread();
        Thread t4 = new MyThread();
        Thread t5 = new MyThread();
        //将线程放入池中进行执行
        pool.execute(t1);
        pool.execute(t2);
        pool.execute(t3);
        pool.execute(t4);
        pool.execute(t5);
        //关闭线程池
        pool.shutdown();
    }
}
运行结果
Connected to the target VM, address: '127.0.0.1:14808', transport: 'socket'
pool-1-thread-1正在执行。。。
pool-1-thread-1正在执行。。。
pool-1-thread-1正在执行。。。
pool-1-thread-1正在执行。。。
pool-1-thread-2正在执行。。。
Disconnected from the target VM, address: '127.0.0.1:14808', transport: 'socket'
Process finished with exit code 0
2、单任务线程池
修改上述实例中的
ExecutorService pool = Executors.newFixedThreadPool(2);
为
//创建一个使用单个worker线程的Executor,以无界队列方式来运行该线程
ExecutorService pool = Executors.newSingleThreadExecutor();
运行结果
pool-1-thread-1正在执行。。。
pool-1-thread-1正在执行。。。
pool-1-thread-1正在执行。。。
pool-1-thread-1正在执行。。。
pool-1-thread-1正在执行。。。
Process finished with exit code 0
3、可变尺寸的线程池
修改实例1中的
ExecutorService pool = Executors.newFixedThreadPool(2);
为
//创建一个可根据需要创建新线程的线程池,但是在以前构造的线程可用时将重用它们。
ExecutorService pool = Executors.newCachedThreadPool();
运行结果
pool-1-thread-1正在执行。。。
pool-1-thread-4正在执行。。。
pool-1-thread-3正在执行。。。
pool-1-thread-2正在执行。。。
pool-1-thread-5正在执行。。。
Process finished with exit code 0
创建自定义线程池的构造方法很多,本例中参数的含义如下:
ThreadPoolExecutor
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue)
用给定的初始参数和默认的线程工厂及处理程序创建新的ThreadPoolExecutor。使用Executors工厂方法之一
比使用此通用构造方法方便得多。
参数:
corePoolSize -池中所保存的线程数,包括空闲线程。
maximumPoolSize -池中允许的最大线程数。
keepAliveTime -当线程数大于核心时,此为终止前多余的空闲线程等待新任务的最长时间。
unit - keepAliveTime参数的时间单位。
workQueue -执行前用于保持任务的队列。此队列仅保持由execute方法提交的Runnable任务。
抛出:
IllegalArgumentException -如果 corePoolSize或 keepAliveTime小于零,或者 maximumPoolSize小于或等
于零,或者 corePoolSize大于 maximumPoolSize。
NullPointerException -如果workQueue为 null
十二、锁
以下为死锁:
public class Test1 {
	public static void main(String[] args) {
		DeadlockRisk1 dead = new DeadlockRisk1();
		MyThread t1 = new MyThread(dead, 1, 2);
		MyThread t2 = new MyThread(dead, 3, 4);
		MyThread t3 = new MyThread(dead, 5, 6);
		MyThread t4 = new MyThread(dead, 7, 8);
		
		t1.start();
		t2.start();
		t3.start();
		t4.start();
	}
}

class MyThread extends Thread {
	private DeadlockRisk1 dead;
	private int a,b;
	
	MyThread(DeadlockRisk1 dead, int a, int b) {
		this.dead = dead;
		this.a = a;
		this.b = b;
	}
	
	@Override
	public void run() {
		dead.read();
		dead.write(a, b);
	}
}


class DeadlockRisk1 {
	private static class Resource {
		public int value;
	}
	
	private Resource resourceA = new Resource();
	private Resource resourceB = new Resource();
	
	public int read() {
		synchronized (resourceA) {
			System.out.println("read():" + Thread.currentThread().getName() + "获取了resourceA的锁!");
			synchronized (resourceB) {
				System.out.println("read():" + Thread.currentThread().getName() + "获取了resourceB的锁!");
				return resourceB.value + resourceA.value;
			}
		}
	}
	
	public void write(int a, int b) {
		synchronized (resourceA) {
			System.out.println("write():" + Thread.currentThread().getName() + "获取到了resourceA的锁!");
			synchronized (resourceB) {
				System.out.println("write():" + Thread.currentThread().getName() + "获取到了resourceB的锁!");
				resourceA.value = a;
				resourceB.value = b;
			}
		}
	}
}
1、普通锁
利用锁可以方便的实现资源的封锁,用来控制对竞争资源并发访问的控制,这些内容主要集中在java.util.concurrent.locks包下面,里面有三个重要的接口Condition、Lock、ReadWriteLock。
Condition
Condition将Object监视器方法(wait、notify和 notifyAll)分解成截然不同的对象,以便通过将这些对象与任意Lock实现组合使用,为每个对象提供多个等待 set(wait-set)。
Lock
Lock实现提供了比使用synchronized方法和语句可获得的更广泛的锁定操作。
ReadWriteLock
ReadWriteLock维护了一对相关的锁定,一个用于只读操作,另一个用于写入操作。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.Lock;
import  java.util.concurrent.locks.ReentrantLock;
/**
 * @author dong
 * @function Java线程:锁
 */
public class Test {
    public static void main(String[] args) {
        //创建并发访问的账户
        MyCount myCount = new MyCount("11111111111111", 1000);
        //创建一个锁对象
        Lock lock = new ReentrantLock();
        //创建一个线程池
        ExecutorService pool = Executors.newCachedThreadPool();
        //创建一些并发访问用户,一个信用卡,存的存,取的取
        User u1 = new User("A", myCount, -400, lock);
        User u2 = new User("B", myCount, 400, lock);
        User u3 = new User("C", myCount, -600, lock);
        User u4 = new User("D", myCount, 100, lock);
        //在线程池中执行各个用户的操作
        pool.execute(u1);
        pool.execute(u2);
        pool.execute(u3);
        pool.execute(u4);
        //关闭线程池
        pool.shutdown();
    }
}

/**
 * 信用卡的用户
 */
class User implements Runnable {
    private String name;      //用户名
    private MyCount myCount;  //索要操作的账户
    private int iocash;       //操作的金额,当然有正负之分了
    private Lock myLock;      //执行操作所需要的锁对象

    User (String name, MyCount myCount, int iocash, Lock myLock) {
        this.name = name;
        this.myCount = myCount;
        this.iocash = iocash;
        this.myLock = myLock;
    }

    public void run() {
        //获取锁
        myLock.lock();
        //执行现金业务
        System.out.println(name + "正在操作" + myCount + "账户,金额为" +
        iocash + ",当前金额为" + myCount.getCash());
        myCount.setCash(myCount.getCash() + iocash);
        System.out.println(name + "操作" + myCount + "账户成功,金额为" +
        iocash + ",当前金额为" + myCount.getCash());
        //释放锁,否则别的线程就没有机会了
        myLock.unlock();
    }
}

/**
 *信用卡账户,可随意透支
 */
class MyCount {
    private String oid;      //账号
    private int cash;        //账户余额
    MyCount (String oid, int cash) {
        this.oid = oid;
        this.cash = cash;
    }

    public String getOid() {
        return oid;
    }
    public void setOid(String oid) {
        this.oid = oid;
    }
    public int getCash () {
        return cash;
    }
    public void setCash(int cash) {
        this.cash = cash;
    }
    public String toString() {
        return "MyCount {" +
                "oid='" + oid + '\'' +
                ", cash=" + cash +
                '}';
    }
}
运行结果
A正在操作MyCount {oid='11111111111111', cash=1000}账户,金额为-400,当前金额为1000
A操作MyCount {oid='11111111111111', cash=600}账户成功,金额为-400,当前金额为600
B正在操作MyCount {oid='11111111111111', cash=600}账户,金额为400,当前金额为600
B操作MyCount {oid='11111111111111', cash=1000}账户成功,金额为400,当前金额为1000
D正在操作MyCount {oid='11111111111111', cash=1000}账户,金额为100,当前金额为1000
D操作MyCount {oid='11111111111111', cash=1100}账户成功,金额为100,当前金额为1100
C正在操作MyCount {oid='11111111111111', cash=1100}账户,金额为-600,当前金额为1100
C操作MyCount {oid='11111111111111', cash=500}账户成功,金额为-600,当前金额为500
Process finished with exit code 0
注意:在获取了锁对象后,用完后应该尽快释放锁,以便别的等待该锁的线程有机会去执行。

2、读写锁
Java中读写锁有个接口java.util.concurrent.locks.ReadWriteLock,也有具体的实现ReentrantReadWriteLock,详细的API可以查看JavaAPI文档。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * @author dong
 * @function Java线程:锁
 */
public class Test {
    public static void main(String[] args) {
        //创建并发访问的账户
        MyCount myCount = new MyCount("11111111111111", 1000);
        //创建一个锁对象
        ReadWriteLock lock = new ReentrantReadWriteLock();
        //创建一个线程池
        ExecutorService pool = Executors.newFixedThreadPool(2);
        //创建一些并发访问用户,一个信用卡,存的存,取的取
        User u1 = new User("A", myCount, -400, lock, false);
        User u2 = new User("B", myCount, 400, lock, false);
        User u3 = new User("C", myCount, -600, lock, false);
        User u4 = new User("D", myCount, 100, lock, false);
        User u5 = new User("B", myCount, 0, lock, true);
        //在线程池中执行各个用户的操作
        pool.execute(u1);
        pool.execute(u2);
        pool.execute(u3);
        pool.execute(u4);
        pool.execute(u5);
        //关闭线程池
        pool.shutdown();
    }
}

/**
 * 信用卡的用户
 */
class User implements Runnable {
    private String name;               //用户名
    private MyCount myCount;           //索要操作的账户
    private int iocash;                //操作的金额,当然有正负之分了
    private ReadWriteLock myLock;      //执行操作所需要的锁对象
    private boolean ischeck;           //是否查询

    User (String name, MyCount myCount, int iocash, ReadWriteLock myLock, boolean ischeck) {
        this.name = name;
        this.myCount = myCount;
        this.iocash = iocash;
        this.myLock = myLock;
        this.ischeck = ischeck;
    }

    public void run() {
        if (ischeck) {
            //获取读锁
            myLock.readLock().lock();
            System.out.println("读: " + name + "正在查询" + myCount + "账户,当前金额为" + myCount.getCash());
            //释放读锁
            myLock.readLock().unlock();
        } else {
            //获取写锁
            myLock.writeLock().lock();
            //执行现金业务
            System.out.println("写: " + name + "正在操作" + myCount + "账户,金额为" +
            iocash + ",当前金额为" + myCount.getCash());
            myCount.setCash(myCount.getCash() + iocash);
            System.out.println("写: " + name + "操作" + myCount + "账户,当前金额为" + myCount.getCash());
            //释放写锁
            myLock.writeLock().unlock();
        }
    }
}

/**
 *信用卡账户,可随意透支
 */
class MyCount {
    private String oid;      //账号
    private int cash;        //账户余额
    MyCount (String oid, int cash) {
        this.oid = oid;
        this.cash = cash;
    }

    public String getOid() {
        return oid;
    }
    public void setOid(String oid) {
        this.oid = oid;
    }
    public int getCash () {
        return cash;
    }
    public void setCash(int cash) {
        this.cash = cash;
    }
    public String toString() {
        return "MyCount {" +
                "oid='" + oid + '\'' +
                ", cash=" + cash +
                '}';
    }
}
运行结果
写: A正在操作MyCount {oid='11111111111111', cash=1000}账户,金额为-400,当前金额为1000
写: A操作MyCount {oid='11111111111111', cash=600}账户,当前金额为600
写: B正在操作MyCount {oid='11111111111111', cash=600}账户,金额为400,当前金额为600
写: B操作MyCount {oid='11111111111111', cash=1000}账户,当前金额为1000
写: C正在操作MyCount {oid='11111111111111', cash=1000}账户,金额为-600,当前金额为1000
写: C操作MyCount {oid='11111111111111', cash=400}账户,当前金额为400
写: D正在操作MyCount {oid='11111111111111', cash=400}账户,金额为100,当前金额为400
写: D操作MyCount {oid='11111111111111', cash=500}账户,当前金额为500
读: B正在查询MyCount {oid='11111111111111', cash=500}账户,当前金额为500
Process finished with exit code 0
注意:在实际开发中,最好在能用读写锁的情况下使用读写锁,而不要用普通锁,以求更好的性能。
十三、信号量
信号量是一个功能完毕的计数器,对控制一定资源的消费与回收有着很重要的意义,信号量常常用于多线程的代码中,并能监控有多少数目的线程等待获取资源,并且通过信号量可以得知可用资源的数目等等,这里总是在强调“数目”二字,但不能指出来有哪些在等待,哪些资源可用。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
/**
 * @author dong 2017-07-10
 * @function Java线程:信号量
 */
public class Test {
    public static void main(String[] args) {
        MyPool myPool = new MyPool(20);
        //创建线程池
        ExecutorService threadPool = Executors.newFixedThreadPool(2);
        MyThread t1 = new MyThread("任务A", myPool, 3);
        MyThread t2 = new MyThread("任务B", myPool, 12);
        MyThread t3 = new MyThread("任务C", myPool, 7);
        //在线程池中执行任务
        threadPool.execute(t1);
        threadPool.execute(t2);
        threadPool.execute(t3);
        //关闭池
        threadPool.shutdown();
    }
}

/**
 * 一个线程池
 */
class MyPool {
    private Semaphore sp;       //池相关的信号量
    /**
     * 池的大小,这个大小会传递给信号量
     * @param size 池的大小
     */
    MyPool(int size) {
        this.sp = new Semaphore(size);
    }
    public Semaphore getSp() {
        return sp;
    }
    public void setSp(Semaphore sp) {
        this.sp = sp;
    }
}

/**
 *信用卡账户,可随意透支
 */
class MyThread extends Thread {
    private String threadname;         //线程的名称
    private MyPool pool;               //自定义池
    private int x;                     //申请信号量的大小

    MyThread(String threadname, MyPool pool, int x) {
        this.threadname = threadname;
        this.pool = pool;
        this.x = x;
    }
    public void run() {
        try {
            //从此信号量获取给定数目的许可
            pool.getSp().acquire(x);
            //todo: 也许这里可以做更复杂的业务
            System.out.println(threadname + "成功获取了" + x + "个许可!");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            //释放给定数目的许可,将其返回到信号量。
            pool.getSp().release(x);
            System.out.println(threadname + "释放了" + x + "个许可!");
        }
    }
}
运行结果:
任务A成功获取了3个许可!
任务A释放了3个许可!
任务C成功获取了7个许可!
任务C释放了7个许可!
任务B成功获取了12个许可!
任务B释放了12个许可!
Process finished with exit code 0
注意:从结果可以看出,信号量仅仅是对池资源进行监控,但不保证线程的安全,因此,在使用时候,应该自己控制线程的安全访问池资源。
十四、阻塞队列
Java定义了阻塞队列的接口java.util.concurrent.BlockingQueue,阻塞队列的概念是,一个指定长度的队列,如果队列满了,添加新元素的操作会被阻塞等待,直到有空位为止。同样,当队列为空时候,请求队列元素的操作同样会阻塞等待,直到有可用元素为止。有了这样的功能,就为多线程的排队等候的模型实现开辟了便捷通道,非常有用。
java.util.concurrent.BlockingQueue继承了java.util.Queue接口。
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ArrayBlockingQueue;
/**
 * @author dong 2017-07-11
 * @function Java线程:阻塞队列
 */
public class Test {
    public static void main(String[] args) throws InterruptedException {
        BlockingQueue bqueue = new ArrayBlockingQueue(20);
        for (int i = 0; i < 30; i++) {
            //将指定元素添加到此队列中,如果没有可用空间,将一直等待(如果有必要)。
            try {
                bqueue.put(i);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("向阻塞队列中添加了元素:" + i);
        }
        System.out.println("程序到此运行结束,即将推出---");
    }
}
运行结果
向阻塞队列中添加了元素:0
向阻塞队列中添加了元素:1
向阻塞队列中添加了元素:2
向阻塞队列中添加了元素:3
向阻塞队列中添加了元素:4
向阻塞队列中添加了元素:5
向阻塞队列中添加了元素:6
向阻塞队列中添加了元素:7
向阻塞队列中添加了元素:8
向阻塞队列中添加了元素:9
向阻塞队列中添加了元素:10
向阻塞队列中添加了元素:11
向阻塞队列中添加了元素:12
向阻塞队列中添加了元素:13
向阻塞队列中添加了元素:14
向阻塞队列中添加了元素:15
向阻塞队列中添加了元素:16
向阻塞队列中添加了元素:17
向阻塞队列中添加了元素:18
向阻塞队列中添加了元素:19
可以看出,输出到元素19时候,就一直处于等待状态,因为队列满了,程序阻塞了。
阻塞队列还有更多实现类,用来满足各种复杂的需求:ArrayBlockingQueue,
/**
 * java线程:条件变量
 * @author dong
 *
 */
public class test6 {
	public static void main(String[] args) {
		//创建并发访问的账户
		MyCount2 myCount = new MyCount2("123456", 10000);
		//创建一个线程池
		ExecutorService pool = Executors.newFixedThreadPool(2);
		Thread t1 = new SavaThread("张三", myCount, 3600);
		Thread t2 = new DrawThread("李四", myCount, 2700);
		Thread t3 = new SavaThread("王五", myCount, 600);
		Thread t4 = new DrawThread("老张", myCount, 1300);
		Thread t5 = new SavaThread("老牛", myCount, 500);
		Thread t6 = new SavaThread("胖子", myCount, 800);
		
		//执行各个线程
		pool.execute(t1);
		pool.execute(t2);
		pool.execute(t3);
		pool.execute(t4);
		pool.execute(t5);
		pool.execute(t6);
		
		//关闭线程池
		pool.shutdown();
	}
}


/**
 * 存款线程
 */
class SavaThread extends Thread {
	private String name;//操作人
	private MyCount2 myCount;//账户
	private int x;//存款金额
	
	public SavaThread(String name, MyCount2 myCount, int x) {
		super();
		this.name = name;
		this.myCount = myCount;
		this.x = x;
	}
	
	public void run() {
		myCount.saving(x, name);
	}
}


/**
 * 取款线程类
 */
class DrawThread extends Thread {
	private String name;//操作人
	private MyCount2 myCount;//账户
	private int x;//存款金额
	
	public DrawThread(String name, MyCount2 myCount, int x) {
		super();
		this.name = name;
		this.myCount = myCount;
		this.x = x;
	}
	
	@Override
	public void run() {
		myCount.drawing(x, name);
	}
	
	 
}




/**
 * 普通银行账户,不可透支
 */
class MyCount2 {
	private String oid;//账号
	private int cash;//账户余额
	private Lock lock = new ReentrantLock();//账户锁
	private Condition _sava = lock.newCondition();//存款条件
	private Condition _draw = lock.newCondition();//取款条件
	public MyCount2(String oid, int cash) {
		super();
		this.oid = oid;
		this.cash = cash;
	}
	
	/**
	 * 存款
	 * @param x 操作金额
	 * @param name 操作人
	 */
	public void saving(int x, String name) {
		lock.lock();//获取锁
		if (x > 0) {
			cash += x;//存款
			System.out.println(name + "存款" + x + ",当前余额为:" + cash);
		}
		_draw.signalAll();//唤醒所有等待线程
		lock.unlock();
	}
	
	
	/**
	 * 取款
	 * @param x  操作金额
	 * @param name 操作人
	 */
	public void drawing(int x, String name) {
		lock.lock();//获取锁
		try {
			if (cash -x < 0) {
				_draw.await();//阻塞取款操作
			} else {
				cash -= x;//取款
				System.out.println(name + "取款:" + x + ",当前余额为:" + cash);
			}
			_sava.signalAll();//唤醒所有取款操作
		} catch (InterruptedException e) {
			e.printStackTrace();
		} finally {
			lock.unlock();//释放锁
		}
	}
}

,
LinkedBlockingQueue, PriorityBlockingQueue, SynchronousQueue,具体的API差别也很小。
十五、条件变量
条件变量都实现了java.util.concurrent.locks.Condition接口,条件变量的实例化是通过一个Lock对象上调用newCondition()方法来获取的,这样,条件就和一个锁对象绑定起来了。因此,Java中的条件变量只能和锁配合使用,来控制并发程序访问竞争资源的安全。
条件变量的出现是为了更精细控制线程等待与唤醒,在java5之前,线程的等待与唤醒依靠的是Object对象的wait()和notify()/notifyAll()方法,这样的处理不够精细。
一个锁可以有多个条件,每个条件上可以有多个线程等待,通过调用await()方法,可以让线程在该条件下等待。当调用signalAll()方法,又可以唤醒该条件下的等待的线程。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * java线程:条件变量
 * @author dong
 *
 */
public class test6 {
	public static void main(String[] args) {
		//创建并发访问的账户
		MyCount2 myCount = new MyCount2("123456", 10000);
		//创建一个线程池
		ExecutorService pool = Executors.newFixedThreadPool(2);
		Thread t1 = new SavaThread("张三", myCount, 3600);
		Thread t2 = new DrawThread("李四", myCount, 2700);
		Thread t3 = new SavaThread("王五", myCount, 600);
		Thread t4 = new DrawThread("老张", myCount, 1300);
		Thread t5 = new SavaThread("老牛", myCount, 500);
		Thread t6 = new SavaThread("胖子", myCount, 800);
		
		//执行各个线程
		pool.execute(t1);
		pool.execute(t2);
		pool.execute(t3);
		pool.execute(t4);
		pool.execute(t5);
		pool.execute(t6);
		
		//关闭线程池
		pool.shutdown();
	}
}

/**
 * 存款线程
 */
class SavaThread extends Thread {
	private String name;//操作人
	private MyCount2 myCount;//账户
	private int x;//存款金额
	
	public SavaThread(String name, MyCount2 myCount, int x) {
		super();
		this.name = name;
		this.myCount = myCount;
		this.x = x;
	}
	
	public void run() {
		myCount.saving(x, name);
	}
}

/**
 * 取款线程类
 */
class DrawThread extends Thread {
	private String name;//操作人
	private MyCount2 myCount;//账户
	private int x;//存款金额
	
	public DrawThread(String name, MyCount2 myCount, int x) {
		super();
		this.name = name;
		this.myCount = myCount;
		this.x = x;
	}
	
	@Override
	public void run() {
		myCount.drawing(x, name);
	}
	
	 
}


/**
 * 普通银行账户,不可透支
 */
class MyCount2 {
	private String oid;//账号
	private int cash;//账户余额
	private Lock lock = new ReentrantLock();//账户锁
	private Condition _sava = lock.newCondition();//存款条件
	private Condition _draw = lock.newCondition();//取款条件
	public MyCount2(String oid, int cash) {
		super();
		this.oid = oid;
		this.cash = cash;
	}
	
	/**
	 * 存款
	 * @param x 操作金额
	 * @param name 操作人
	 */
	public void saving(int x, String name) {
		lock.lock();//获取锁
		if (x > 0) {
			cash += x;//存款
			System.out.println(name + "存款" + x + ",当前余额为:" + cash);
		}
		_draw.signalAll();//唤醒所有等待线程
		lock.unlock();
	}
	
	
	/**
	 * 取款
	 * @param x  操作金额
	 * @param name 操作人
	 */
	public void drawing(int x, String name) {
		lock.lock();//获取锁
		try {
			if (cash -x < 0) {
				_draw.await();//阻塞取款操作
			} else {
				cash -= x;//取款
				System.out.println(name + "取款:" + x + ",当前余额为:" + cash);
			}
			_sava.signalAll();//唤醒所有取款操作
		} catch (InterruptedException e) {
			e.printStackTrace();
		} finally {
			lock.unlock();//释放锁
		}
	}
}
如果不使用锁和条件变量,实现上述功能,如下所示:
/**
 * java线程:不用条件变量
 * @author dong
 *
 */
public class test7 {
	public static void main(String[] args) {
		//创建并发访问的账户
		MyCount3 myCount = new MyCount3("123456", 10000);
		//创建一个线程池
		ExecutorService pool = Executors.newFixedThreadPool(2);
		Thread t1 = new SaveThread1("张三", myCount, 2000);
		Thread t2 = new SaveThread1("李四", myCount, 3600);
		Thread t3 = new DrawThread1("王五", myCount, 5000);
		Thread t4 = new SaveThread1("老牛", myCount, 1100);
		Thread t5 = new DrawThread1("老张", myCount, 1500);
		//执行各个线程
		pool.execute(t1);
		pool.execute(t2);
		pool.execute(t3);
		pool.execute(t4);
		pool.execute(t5);
		//关闭线程池
		pool.shutdown();
	}
}


/**
 * 存款线程
 */
class SaveThread1 extends Thread {
	private String name;//操作人
	private MyCount3 myCount;//账户
	private int x;//存款金额
	public SaveThread1(String name, MyCount3 myCount, int x) {
		super();
		this.name = name;
		this.myCount = myCount;
		this.x = x;
	}
	
	@Override
	public void run() {
		myCount.saving(x, name);
	}
}

/**
 * 取款线程
 */
class DrawThread1 extends Thread {
	private String name;//操作人
	private MyCount3 myCount;//账户
	private int x;//存款金额
	public DrawThread1(String name, MyCount3 myCount, int x) {
		super();
		this.name = name;
		this.myCount = myCount;
		this.x = x;
	}
	
	@Override
	public void run() {
		myCount.drawing(x, name);
	}
	
	
}


/**
 * 普通银行卡账户,不可透支
 */
class MyCount3 {
	private String oid;//账号
	private int cash;//账户余额
	public MyCount3(String oid, int cash) {
		super();
		this.oid = oid;
		this.cash = cash;
	}
	
	/**
	 * 存款
	 * @param x 操作金额
	 * @param name 操作金额
	 */
	public synchronized void saving(int x, String name) {
		if (x > 0) {
			cash += x;
			System.out.println(name + "存款" + x + ",当前余额为:" + cash);
		}
		notifyAll();
	}
	
	/**
	 * 取款
	 * @param x 操作金额
	 * @param name 操作人
	 */
	public synchronized void drawing(int x, String name) {
		if (cash - x < 0) {
			try {
				wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}			
		} else {
			cash -= x;//取款
			System.out.println(name + "取款" + x + "当前余额为:" + cash);
		}
	}
}
用同步方式,实现上述功能,如下附件所示:
/**
 * java线程:不用条件变量,改为同步代码块
 * @author dong
 *
 */
public class test8 {
	public static void main(String[] args) {
		//创建并发访问的账户
		MyCount3 myCount = new MyCount3("123456", 10000);
		//创建一个线程池
		ExecutorService pool = Executors.newFixedThreadPool(2);
		Thread t1 = new SaveThread1("张三", myCount, 2000);
		Thread t2 = new SaveThread1("李四", myCount, 3600);
		Thread t3 = new DrawThread1("王五", myCount, 5000);
		Thread t4 = new SaveThread1("老牛", myCount, 1100);
		Thread t5 = new DrawThread1("老张", myCount, 1500);
		//执行各个线程
		pool.execute(t1);
		pool.execute(t2);
		pool.execute(t3);
		pool.execute(t4);
		pool.execute(t5);
		//关闭线程池
		pool.shutdown();
	}
}


/**
 * 存款线程
 */
class SaveThread2 extends Thread {
	private String name;//操作人
	private MyCount3 myCount;//账户
	private int x;//存款金额
	public SaveThread2(String name, MyCount3 myCount, int x) {
		super();
		this.name = name;
		this.myCount = myCount;
		this.x = x;
	}
	
	@Override
	public void run() {
		myCount.saving(x, name);
	}
}

/**
 * 取款线程
 */
class DrawThread2 extends Thread {
	private String name;//操作人
	private MyCount3 myCount;//账户
	private int x;//存款金额
	public DrawThread2(String name, MyCount3 myCount, int x) {
		super();
		this.name = name;
		this.myCount = myCount;
		this.x = x;
	}
	
	@Override
	public void run() {
		myCount.drawing(x, name);
	}
	
	
}


/**
 * 普通银行卡账户,不可透支
 */
class MyCount4 {
	private String oid;//账号
	private int cash;//账户余额
	public MyCount4(String oid, int cash) {
		super();
		this.oid = oid;
		this.cash = cash;
	}
	
	/**
	 * 存款
	 * @param x 操作金额
	 * @param name 操作金额
	 */
	public synchronized void saving(int x, String name) {
		if (x > 0) {
			synchronized (this) {
				cash += x;//存款
				System.out.println(name + "存款" + x + ",当前余额为:" + cash);
				notifyAll();//唤醒所有等待的线程
			}	
		}
	}
	
	/**
	 * 取款
	 * @param x 操作金额
	 * @param name 操作人
	 */
	public synchronized void drawing(int x, String name) {
		synchronized (this) {
			if (cash - x < 0) {
				try {
					wait();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}			
			} else {
				cash -= x;//取款
				System.out.println(name + "取款" + x + "当前余额为:" + cash);
			}
			notifyAll();//唤醒所有的存款操作
		}
	}
}
对比以上三种方式,从控制角度上讲,第一种最灵活,第二种代码最简单,第三种容易犯错。
十六、原子量
原子量的操作是“原子的”,该操作不可再分因此是线程安全的。在java5之前,可以通过volatile、synchronized关键字来解决并发访问的安全问题,但比较麻烦。java5之后提供了用来进行单变量多线程并发安全访问的工具包java.util.concurrent.atomic,其中的类也简单。
public class test9 {
	public static void main(String[] args) {
		ExecutorService pool = Executors.newFixedThreadPool(2);
		Lock lock = new ReentrantLock(false);
		Runnable t1 = new MyRunnable3("张三", 2000, lock);
		Runnable t2 = new MyRunnable3("李四", 3600, lock);
		Runnable t3 = new MyRunnable3("王五", 2700, lock);
		Runnable t4 = new MyRunnable3("老张", 600, lock);
		Runnable t5 = new MyRunnable3("老牛", 1300, lock);
		Runnable t6 = new MyRunnable3("胖子", 800, lock);
		//执行各个线程
		pool.execute(t1);
		pool.execute(t2);
		pool.execute(t3);
		pool.execute(t4);
		pool.execute(t5);
		pool.execute(t6);
		//关闭线程池
		pool.shutdown();
	}
}

class MyRunnable3 implements Runnable {
	private static AtomicLong aLong = new AtomicLong(1000);//原子量,每个线程都可以自由操作
	private String name; //操作人
	private int x;//操作数额
	private Lock lock;
	
	public MyRunnable3(String name, int x, Lock lock) {
		super();
		this.name = name;
		this.x = x;
		this.lock = lock;
	}


	@Override
	public void run() {
		lock.lock();
		System.out.println(x + "执行了" + x + ",当前余额为:" + aLong.addAndGet(x));
		lock.unlock();
		
	}
	
}
有关原子量的用法很简单,关键是对原子量的认识,原子仅仅是保证变量操作的原子性,但整个程序还需要考虑线程安全的。
十七、障碍器
Java5中,添加了障碍器类,为了适应一种新的设计需求,比如一个大型的任务,常常需要分配好多子任务去执行,只有当所有子任务都执行完成时候,才能执行主任务,这时候,就可以选择障碍器了。
实例见如下:
/**
 * java线程:障碍器
 * @author dong
 *
 */
public class test10 {
	public static void main(String[] args) {
		//创建障碍器,并设置MainTask为所有定数量的线程都达到障碍点的时候所要执行的任务(Runnable)
		CyclicBarrier cb = new CyclicBarrier(7, new MainTask());
		new SubTask("A", cb).start();
		new SubTask("B", cb).start();
		new SubTask("C", cb).start();
		new SubTask("D", cb).start();
		new SubTask("E", cb).start();
		new SubTask("F", cb).start();
		new SubTask("G", cb).start();
		
	}
}

/**
 * 主任务
 */
class MainTask implements Runnable {

	@Override
	public void run() {
		System.out.println(">>>>>>主任务执行了<<<<<<<");
	}
}

/**
 *子任务
 */
class SubTask extends Thread {
	private String name;
	private CyclicBarrier cb;
	
	public SubTask(String name, CyclicBarrier cb) {
		super();
		this.name = name;
		this.cb = cb;
	}
	
	@Override
	public void run() {
		System.out.println("[子任务" + name + "]开始执行了!");
		for (int i = 0; i < 99999; i++);//模拟耗时任务
		System.out.println("[子任务" + name + "]开始执行完成了,并通过障碍器已经完成!");
		try {
			//通知障碍器已经完成
			cb.await();
		} catch (InterruptedException e) {
			e.printStackTrace();
		} catch (BrokenBarrierException e) {
			e.printStackTrace();
		}
	}
}
fatal: unable to access ‘https://github.com/Stability-AI/stablediffusion.git/‘解决方案 本文主要介绍在安装 stable-diffusion-webui 时出现的 fatal: unable to access 'https://github.com/Stability-AI/stablediffusion.git/': Failed to connect to github.com port 443 after 21071 ms: Couldn't connect to server 问题的解决方案,希望能对你有所帮助。 阅读详情

相关推荐

C#实战:如何用winmm.dll打造毫秒级高精度定时器(附完整代码)

本文详细介绍了在C#中利用winmm.dll实现毫秒级高精度定时器的完整方案。针对.NET内置定时器精度不足的问题,通过调用timeSetEvent等原生API,提供了超越系统时钟中断周期的精准定时能力。文章包含核心原理剖析、完整的封装类代码以及音视频处理、游戏开发等场景下的实战应用与性能调优指南。

ujm5678901的博客 689

JAVA线程

虽然我们可以理解同步代码块和同步方法的锁对象问题,但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁,为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock,Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来实例化。

互联网底层民工的博客 1万+

告别信号毛刺!手把手教你用ADS和IBIS-AMI模型搞定PCIe Gen3眼图仿真

本文详细介绍了如何使用Keysight ADS和IBIS-AMI模型进行PCIe Gen3眼图仿真,解决高速信号传输中的信号质量问题。从仿真环境搭建、S参数预处理到眼图优化与故障排除,提供了一套完整的工程实践指南,帮助工程师提升PCIe Gen3接口的信号完整性。

weixin_28366053的博客 362

Java线程:什么是线程

一 基本概念   多任务:同一时刻运行多个程序的能力。每一个任务称为一个线程。可以同时运行一个以上线程的程序称为多线程程序。   Java编写程序都运行在在Java虚拟机(JVM)中,在JVM的内部,程序的多任务是通过线程来实现的。每用java命令启动一个java应用程序,就会启动一个JVM进程。在同一个JVM进程中,有且只有一个进程,就是它自己。在这个JVM环境中,所有程序代码的运行都是以线

arredando的博客 3万+

【GAN】pytorch-CycleGAN-and-pix2pix开源项目训练自定义数据集

Github:https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix 一、概念简介 图像转换:将一张图片转换为不同风格的图片,如将一张灰度图变成彩色图,素描图换成实物图。 pix2pix:需要成对的数据集,两个不同域的图像一一对应。 CycleGAN:不需要成对的数据集,只用两个不同域(domain)的图像集合X和Y,两个集合中的图像不是一一对应的。 二、训练pix2pix模型 2.1 combine_A_and_B datasets/combin

See_Star的博客 4755

IPD的分析工具与模型(十六)CBB公用基础模块管理实现资源共享,缩短开发周期

CBB流程的开发环节,基于CBB规划中所识别和设计的CBB,进行CBB的详细设计(包括参数设计和接口设计),并最终实现CBB的落地开发。作为特殊类型的技术构件,CBB的开发流程,可沿用技术开发中的构件开发流程,具体包括概念、计划、开发和迁移等步骤,具体可参见前文的相关介绍。有了蓝图与架构设计,落地实施是关键攻坚。具体工作内容包括:具体工作内容包括:a. 以相似性、聚类、相对独行性、可组合、可扩展性、可维护性等原则为指导,按照产品的功能特征和业务类型,聚焦零散的功能点,形成功能模块,实现产品功能模块的划分;

corgi2020的博客 2000

【线程】Java线程操作

讲述了线程的启动、终止、等待操作,还有线程的各种状态。

telasi2004的博客 2062

解决UNITE数据库注释高unassigned率:手把手教你训练专属真菌分类器(含预训练模型分享)

本文针对使用UNITE数据库进行真菌ITS序列注释时出现的高未分配率问题,提供了详细的实战解决方案。通过对比vsearch直接注释与Naive Bayes分类器的原理差异,文章手把手指导读者在QIIME 2环境中训练专属分类器,包括数据准备、模型训练、参数优化及效果验证,并分享了预训练模型,旨在显著提升真菌群落分析的注释准确性和数据利用率。

weixin_29214199的博客 338

1、【java线程及线程池系列】java线程及线程池概念详解

缓冲队列长度的设置,按照一般的理解应该设置成maximumPoolSize的5倍,因为单个线程在不切换cpu的情况下是单个cpu运算一个线程,corePoolSize设置成和cpu一样的数量,maximumPoolSize设置成cpu的2倍,避免cpu处于空闲状态则直接加入一旦cpu空闲理解执行的线程数量,同时由于cpu的速度较快以及有请求执行的线程加入缓冲队列中减少cpu等待时间,故将缓冲队列设置大一些。线程池存在的目的在于提前创建好需要的线程,减少临时创建线程带来的资源消耗。

alanchanchn的专栏 8万+

生成mm grounding dino的onnx模型并在x-anylabeling中使用

X-AnyLabeling 是一款cvhub开发的交互式自动标注工具,其基于AnyLabeling进行构建和二次开发,在此基础上扩展并支持了许多的模型和功能,并借助Segment Anything和YOLO等主流模型提供强大的 AI 支持。其最新版基于grounding dino可以实现开放词汇检测及生成掩码。2024年,MMDet 3.3.0提出了 MM-Grounding-DINO。

blanokvaffy的博客 1728

【国密】基于SM2的协同签名与协同解密方案

两方协同签名指客户端与服务端协同完成签名、验签操作,具体实现方案参考了信工所专利[3]。在业务应用中,两方或多方的协同签名一般采用门限签名方案可以用来构造。门限密码算法在多方协作的相关场景中应用十分广泛,可以实现数据联合授权、认证、密钥安全恢复、密钥安全交换等需求,在诸多分布式多方计算协议中均可见其身影。在上述两方协同签名方案中,最巧妙的地方在于签名值s分量的构造,通过一些列数学变换,得以恢复。以下从两方协同签名方案介绍入手,介绍门限密码在协同签名中的应用。解决上述问题的关键在于门限密码算法的巧妙构造。

听雨草堂 8387

Java多线程——认识线程

Java多线程初阶—— 认识线程

YumunaoDAII的博客 1631

文档:亲测可用解决vnc灰屏,黑屏,鼠标大黑叉情况

主要解决vnc-viewer灰屏,黑屏,鼠标大黑叉等现象,详细见文档。

Java创建线程

既然说到线程,在这里就给大家普及一下线程。 线程(Thread): 是操作系统能够进行运算调度的最小单位。 它被包含在进程之中,是进程中的实际运作单位。 一个线程指的是进程中一个单一顺序的控制流。 🥂🥂🥂 其实:进程在我们计算机中我们随时都在使用:按住 Ctrl + shift + esc 这个就是一个进程,在计算机中,线程就是进程中的一个执行单元。 那么,进程是什么呢? 进程:简简单单,我们日常中使用的QQ就是一个进程,进程可以理解为就是一个软件,线程则就是这个软件中的一些功

分享it知识! 7911

IEM模型,地表粗糙度模拟

利用微波反演土壤水分时候,利用IEM模型可以实现对随机地表的模拟

Camera2Demo

在Camera2 的基础上进行封装,使得Camera2一堆初始化逻辑与Activity分离,降低耦合度,完成预览拍照并保存的操作

JAVA线程、线程池解析

是操作系统能够进行的最小单位,它被包含在进程之中,是进程中的实际运作单位。进程是资源分配的基本单位,它拥有自己独立的地址空间和各种资源;而线程是处理机调度的基本单位,它只能和其他线程共享进程的资源,而。线程的上下文切换速度比进程快。

柔丽君的博客 1413

IDEA的使用指南

IDEA基于java的使用手册,可以最快速的上手,在使用中不断的丰富自己的使用技巧.

Java 线程机制

Java 的线程机制是 Java 平台的核心特性之一,它允许程序同时执行多个任务。下面我将详细介绍 Java 线程的基本概念、创建方法、生命周期以及同步机制。

weixin_47363690的博客 1876

Java线程与内核线程

本篇文章探究下Java线程与内核线程的关系. 在Java中,一个Java的线程对应一个内核的线程,实际的业务代码是由内核线程来执行的,而Java线程只是一个傀儡. 先通过一个简单的实验热热身 import java.lang.Thread; public class Example { public static void main(String[] args) throws Exception { new Thread(new Runnable() { @O

书唐瑞的博客 975

Java笔记——java 线程详解_java线程

此时就用这三个方法来灵活控制。并且jdk官方人员发现,很多线程在等待锁的时候,在很短的一段时间就获得了锁,所以它们在线程等待的时候,并不需要把线程挂起,而是让他无目的的循环,一般设置10次。例如有两个线程同时执行(没有synchronized)一个线程优先级为MAX_PRIORITY,另一个为MIN_PRIORITY,如果没有Sleep()方法,只有高优先级的线程执行完毕后,低优先级的线程才能够执行;thread.join把指定的线程加入到当前线程,可以将两个交替执行的线程合并为顺序执行的线程。

a25847a的博客 1341

Java线程,多线程,线程同步,以及Java回调线程,还有难懂的synchronized

曾几何时,有多少人听到了多线程就某花一紧,听到了线程的同步就感觉昏昏欲睡。那我们今天就聊一聊我的理解。线程是什么?Java中常用到的新建线程的场景?以及多线程和线程池的概念,最牛逼的还是要说清楚线程的同步。我跟人认为在Java的线程开发中,如果没有搞清楚同步的使用和JVM到底是什么机制实现的同步(本文中只针对synchronized的同步做讲解lock另写)。 废话不多说了,什么是线程这个问题大

Kyunchen的专栏 4485

java线程池停止线程_如何在Java中停止线程?

java线程池停止线程 停止线程 (Stopping a thread) As we know that there are no direct or shortcut ways to stop thread in Java. 众所周知,在Java中没有直接或快捷的方式来停止线程。 As we know thread in java stops when the execution of run...

cumtb2009的博客 5814

Java多线程之线程

关系多了,错综复杂,千头万绪,往往出现“万事俱备,只欠东风”的情况。获往往因为抓的不是关键,连夜三班,急急忙忙,完成这一环节之后,还得等待旁的环节才能装配。当Context Switch 发生时,需要由操作系统保持当前线程的状态,并恢复另一个线程的状态,Java 中对应的概念就是程序计数器(Program Counter Register), 它的作用是记住下一条jvm指令的执行地址,是线程私有的。有一种特殊的线程叫做守护线程,只要其它非守护线程运行结束了,即使守护线程的代码没有执行完,也会强制结束。

故里有青山,而我心里有你 767

JAVA线程详解

目录 线程简介 线程实现 JAVA线程调度 JAVA线程的状态 JAVA新建线程 JAVA线程Thread类 AVA线程属性 线程安全 线程简介 操作系统运行一个程序时,会为其创建一个进程,一个进程里可以创建多个线程,线程为操作系统调度的最小单位,每个线程拥有各自的计数器、堆栈、、局部变量等属性,并且可以访问共享变量,CPU在这些线程上高速切换调度执行,早期CPU由单核实现,同...

qq_18581221的博客 7407

Java并发——线程与Java线程

前言 对于大多数Java开发人员而言,在实际工作中很少遇到复杂的并发场景。最多可能也就用一下线程池来并发运行程序以提升执行效率。 虽然现状如此,但这并非意味着我们简单掌握并发的应用就可以了。在当今摩尔定律失效的情况下,并发处理成为了计算机发展的新动力,也成为了人类压榨CPU运算能力最有利的武器! 基于以上的认知,我认为从思想和底层基础的角度来系统学习Java并发知识会比零零散散的的学习和简单应用能够得到更高的收益。 本篇文章将系统介绍Java并发的基础条件——线程。 线程 首先,我们必须深刻认识到,在计算机

兵临奇点 348

java中操作线程

java中创建、查看。线程暂停,打断。线程状态等使用方法总结。

su_xu_chao的博客 904

Java线程实现和线程状态

写在最前 之前写了ConcurrentHashMap,无论是1.8还是1.7,其中都会涉及锁Synchronized和ReentrantLock;锁的概念是为了解决线程安全问题;所以还是从线程开始写起。 这篇文章主要是写写Java线程的实现和线程的状态,以及上下文的概念,也有一些不完善的地方,会持续查缺补漏。 Java线程的实现 Java线程模型是基于操作系统原生线程模型的实现,在JDK中,win...

diaog的博客 1429

Java线程

线程(Thread)是操作系统调度的最小单元。每个进程至少有一个主线程,Java 程序本身就是多线程运行的。Java 中线程是轻量级的进程,多个线程共享同一个进程的资源(如堆内存),但拥有自己的栈空间。

m0_46168903的博客 1378
上一篇: 适配器模式(Adapter)
下一篇: 外观模式(Facade)
水之光love
博客等级 码龄12年 56粉丝 29原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值