第十三章 线程
13、多线程 | |
13.1 程序、进程、线程的概念 | 1课时 |
13.2 Java中多线程的创建和使用 | 1课时 |
13.3 线程的生命周期 | 1课时 |
13.4 线程的同步 | 1课时 |
13.5 线程的通信 | 1课时 |
13.6 线程池 | 1课时 |
案例
//如下程序不是多线程的。一个线程可以理解为一条执行路径。
public class MainTest {
public static void method1(String info){
System.out.println(info);
}
public static void method2(String info){
System.out.println(info);
method1(info);
}
public static void main(String[] args) {
method2("今天天气不错!");
}
}
案例2
/*
* 创建一个分线程,遍历100以内的偶数
*
* 一、继承Thread的方式
* 1.提供一个继承于Thread类的子类
* 2.重写Thread类的run():将创建的线程要执行的操作,声明在run()中。
* 3.实例化Thread子类
* 4.调用子类对象的start()
*
*/
//1.提供一个继承于Thread类的子类
class NumThread extends Thread{
//2.重写Thread类的run():将创建的线程要执行的操作,声明在run()中。
public void run() {
for(int i = 1;i <= 100;i++){
if(i % 2 == 0){
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}
public class ThreadTest {
public static void main(String[] args) {
//3.实例化Thread子类
NumThread thread1 = new NumThread();
//4.调用子类对象的start():①启动线程 ②调用当前线程的run()
thread1.start();
//问题一:能不能直接调用run(),去启动分线程?不能!
// thread1.run();
//问题二:再启动一个分线程,仍然遍历100以内的偶数.如下的写法是错误的
// thread1.start();
//正确的写法:新建NumThread的对象
NumThread thread2 = new NumThread();
thread2.start();
for(int i = 1;i <= 100;i++){
if(i % 2 == 0){
System.out.println(Thread.currentThread().getName() + ":" + i + "****************");
}
}
}
}
案例3
/**
* 创建多线程的方式二:实现Runnable接口
* 1.创建一个实现Runnable接口的类
* 2.实现Runnable中的run()
* 3.创建当前实现类的对象
* 4.将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
* 5.通过Thread类的对象调用其start()
*
* 对比继承Thread类和实现Runnable接口两种方式?
* 1.联系:public class Thread implements Runnable
* 2.相同点:启动线程,使用的是同一个start()方法
*
* 3.对比:实现Runnable接口更好一些。
* 原因:1)不影响类的继承。因为类是单继承的。
* 2)针对于有共享数据的操作,更适合使用Runnable的方式。
* 换句话说,实现Runnable接口的方式,实现了代码和数据的分离。
*
* 4.面试题:创建多线程有几种方法?4种!
* 继承Thread类;实现Runnable接口;实现Callable接口;使用线程池;
*/
// 1.创建一个实现Runnable接口的类
class Num implements Runnable{
//2.实现Runnable中的run()
@Override
public void run() {
for(int i = 1;i <= 100;i++){
if(i % 2 == 0){
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}
public class ThreadTest1 {
public static void main(String[] args) {
//3.创建当前实现类的对象
Num num = new Num();
//4.将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
Thread t1 = new Thread(num);
//5.通过Thread类的对象调用其start():①启动线程 ②调用此线程的run()
t1.start();//start()--->Thread的run()--->Num的run()
//再创建一个线程,遍历100以内的偶数
Thread t2 = new Thread(num);
t2.start();
}
}
案例4
/**
* Thread类的常用方法的测试
* 1.run():Thread的子类一定要重写的方法。将此分线程要执行的操作,声明在run()中
* 2.start():要想启动一个分线程,就需要调用start():①启动线程②调用线程的run()
* 3.currentThread():静态方法,获取当前的线程
* 4.getName():获取当前线程的名字
* 5.setName(String name):设置当前线程的名字
* 6.yield():当前线程调用此方法,释放CPU的执行权
* 7.join():在线程a中调用线程b的join()方法:只用当线程b执行结束以后,线程a结束阻塞状态,继续执行。
* 8.sleep(long millitimes):让当前的线程睡眠millitimes毫秒
* 9.isAlive():判断当前线程是否存活
*
* 10.线程的优先级:
* MAX_PRIORITY:10
* NORM_PRIORITY:5 ---默认优先级
* MIN_PRIORITY:1
*
* 设置优先级:setPriority(int priority);
* 获取优先级:getPriority();
*
* 设置优先级以后,对高优先级,使用优先调度的抢占式策略,抢占低优先级的执行。但是并不意味着高优先级的线程一定先于低
* 优先级的线程执行,而是从概率上来讲,概率更大而已。
*
*
* 线程通信:wait() / notify() / notifyAll() ---->java.lang.Object类中定义的方法
*
*
*/
class NumberThread extends Thread{
public void run() {
for(int i = 1;i <= 100;i++){
if(i % 2 == 0){
// try {
// Thread.sleep(10);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
System.out.println(Thread.currentThread().getName()+ ":" + Thread.currentThread().getPriority() + ":" + i);
}
// if(i % 20 == 0){
// yield();
// }
}
}
public NumberThread(){}
public NumberThread(String name){
super(name);
}
}
public class ThreadMethodTest {
public static void main(String[] args) {
NumberThread t1 = new NumberThread();
t1.setName("分线程1");
//设置t1的优先级
t1.setPriority(Thread.MAX_PRIORITY);
t1.start();
// NumberThread t2 = new NumberThread("分线程2");
// t2.start();
Thread.currentThread().setName("主线程");
for(int i = 1;i <= 100;i++){
if(i % 2 == 0){
System.out.println(Thread.currentThread().getName() + ":" + Thread.currentThread().getPriority() + ":" + i);
}
// if(i == 20){
// try {
// t1.join();
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// }
}
// System.out.println(t1.isAlive());
}
}
案例5
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
/*创建多线程的方式:
1.继承Thread
2.实现Runnable
3.实现Callable
4.使用线程池
*/
class MyThread01 extends Thread {
@Override
public void run() {
System.out.println("-----MyThread01");
}
}
class MyThread02 implements Runnable {
public void run() {
System.out.println("-----MyThread02");
}
}
class MyThread03 implements Callable<Integer> {
@Override
public Integer call() throws Exception {
System.out.println("-----MyThread03");
return 200;
}
}
public class ThreadNew {
public static void main(String[] args) {
new MyThread01().start();
new Thread(new MyThread02()).start();
FutureTask futureTask = new FutureTask(new MyThread03());
new Thread(futureTask).start();
}
}
案例6
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
//创建并使用多线程的第四种方法:使用线程池
class MyThread implements Runnable {
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
public class ThreadPool {
public static void main(String[] args) {
//1.调用Executors的newFixedThreadPool(),返回指定线程数量的ExecutorService
ExecutorService pool = Executors.newFixedThreadPool(10);
//2.将Runnable实现类的对象作为形参传递给ExecutorService的submit()方法中,开启线程
//并执行相关的run()
pool.submit(new MyThread());//线程.start()
pool.submit(new MyThread());
pool.submit(new MyThread());
//3.结束线程的使用
pool.shutdown();
}
}
13-2 Java中多线程的创建和使用
练 习
创建两个子线程,让其中一个输出1-100之间的偶数,另一个输出1-100之间的奇数。
练 习
/**
* 创建两个分线程,线程一:遍历100以内的偶数,线程二:遍历100以内的奇数
*
*/
public class ThreadExer {
public static void main(String[] args) {
//方式一:
// MyThread1 t1 = new MyThread1();
// MyThread2 t2 = new MyThread2();
//
// t1.start();
// t2.start();
//方式二:创建Thread类的匿名子类的匿名对象
new Thread(){
public void run() {
for(int i = 1;i <= 100;i++){
if(i % 2 == 0){
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}.start();
new Thread(){
public void run() {
for(int i = 1;i <= 100;i++){
if(i % 2 != 0){
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}.start();
}
}
class MyThread1 extends Thread{
@Override
public void run() {
for(int i = 1;i <= 100;i++){
if(i % 2 == 0){
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}
class MyThread2 extends Thread{
@Override
public void run() {
for(int i = 1;i <= 100;i++){
if(i % 2 != 0){
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}
守护线程
/**
* @author Heaton
* @email tzy70416450@163.com
* @date 2018/10/16 0016 16:28
* @describe
* 当我们在Java中创建一个线程,缺省状态下它是一个User线程,如果该线程运行,JVM不会终结该程序。
* 当一个线被标记为守护线程,JVM不会等待其结束,只要所有用户(User)线程都结束,JVM将终结程序及相关联的守护线程。
* Java中可以用 Thread.setDaemon(true) 来创建一个守护线程。咱们看一个Java中有关守护线程的例子。
*
*
*/
public class JavaDaemonThread {
public static void main(String[] args) throws InterruptedException {
Thread dt = new Thread(new DaemonThread(), "dt");
// dt.setDaemon(true);//此次将User线程变为Daemon线程
dt.start();
//程序继续
Thread.sleep(30000);
System.out.println("程序结束");
}
}
class DaemonThread implements Runnable{//此类虽类名是为Daemon线程,其实为User线程
@Override
public void run() {
while(true){
processSomething();
}
}
private void processSomething() {
try {
System.out.println("守护线程");
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
当你运行该程序,JVM 在main()方法中先创建一个用户线程,再创建一个守护线程。当main()方法结束后,程序终结,同时JVM也关闭守护线程。
下面就是上述程序执行的结果:
如果我们不将一个线程以守护线程方式来运行,即使主线程已经执行完毕,程序也永远不会结束,可以尝试把上述将线程设为守护线程的那句注释掉,重新运行看看结果:
通常我们创建一个守护线程,对于一个系统来说在功能上不是主要的。例如抓取系统资源明细和运行状态的日志线程或者监控线程。
13-3 线程的生命周期
13-4 线程的同步
例 题
模拟火车站售票程序,开启三个窗口售票。
例 题解法一
/**
*
* 模仿车站买票程序。开启3个窗口卖票。总票数为100张。-----使用继承的方式
*
* 此题目中存在线程的安全问题,待解决。
*/
public class WindowTest {
public static void main(String[] args) {
Window w1 = new Window();
Window w2 = new Window();
Window w3 = new Window();
w1.setName("窗口1");
w2.setName("窗口2");
w3.setName("窗口3");
w1.start();
w2.start();
w3.start();
}
}
class Window extends Thread{
static int ticket = 100;
@Override
public void run() {
while(true){
if(ticket > 0){
System.out.println(Thread.currentThread().getName() + "售票,票号为:" + ticket);
ticket--;
}else{
break;
}
}
}
}
例 题解法二
/**
* 模仿车站买票程序。开启3个窗口卖票。总票数为100张。-----使用实现的方式
*
* 仍然存在线程的安全问题,待解决
*/
public class WindowTest {
public static void main(String[] args) {
Window w = new Window();
Thread t1 = new Thread(w);
Thread t2 = new Thread(w);
Thread t3 = new Thread(w);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
class Window implements Runnable{
int ticket = 100;
@Override
public void run() {
while(true){
if(ticket > 0){
System.out.println(Thread.currentThread().getName() + "售票,票号为:" + ticket);
ticket--;
}else{
break;
}
}
}
}
线程安全1
/**
*
* 模仿车站买票程序。开启3个窗口卖票。总票数为100张。-----使用实现的方式
*
* 1.问题:出现了重票和错票
* 2.问题出现的原因:一个窗口在没有售完票的情况下,其他的窗口参与进来,操作ticket,导致ticket输出的错误。
* 3.解决的方案:当某一个窗口完全操作完ticket以后,其他窗口应该才被允许进来,继续操作ticket。
* 4.java如何实现的?同步机制:①同步代码块 ②同步方法
*
* 4.1 同步代码块:
* synchronized(同步监视器){
* //需要被同步的代码
* }
* 说明:需要被同步的代码:即为操作共享数据的代码
* 共享数据:多个线程共同操作的数据。比如:ticket
* 同步监视器:俗称:锁。 可以由任何一个类的对象充当。
* 要求:保证多个线程共用同一把锁!
* 4.2 同步方法:将操作共享数据的方法,声明为同步的。此方法即为同步方法。
*
* 5. 好处:线程的同步机制,解决了线程的安全问题。
* 6. 劣势:在操作共享数据过程中,是单线程的。
*/
public class WindowTest {
public static void main(String[] args) {
Window w = new Window();
Thread t1 = new Thread(w);
Thread t2 = new Thread(w);
Thread t3 = new Thread(w);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
class Window implements Runnable{
int ticket = 100;
// Object obj = new Object();
Dog dog = new Dog();
@Override
public void run() {
while(true){
// synchronized(dog){
// synchronized(new Dog()){
synchronized(this){//this:当前的对象:w
if(ticket > 0){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "售票,票号为:" + ticket);
ticket--;
}else{
break;
}
}
}
}
}
class Dog{
}
线程安全2
/**
*
* 模仿车站买票程序。开启3个窗口卖票。总票数为100张。-----使用继承的方式
*
* 使用同步代码块的方式解决继承中的线程安全问题。
*/
public class WindowTest1 {
public static void main(String[] args) {
Window1 w1 = new Window1();
Window1 w2 = new Window1();
Window1 w3 = new Window1();
w1.setName("窗口1");
w2.setName("窗口2");
w3.setName("窗口3");
w1.start();
w2.start();
w3.start();
}
}
class Window1 extends Thread {
static int ticket = 100;
static Object obj = new Object();
@Override
public void run() {
while (true) {
// synchronized (obj) {//正确的。此时的obj是唯一的
// synchronized(this){//this:此时表示为:w1,w2,w3。不唯一。
synchronized(Window1.class){//Class clazz = Window1.class;//此时的Window1.class也是唯一的。
if (ticket > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "售票,票号为:" + ticket);
ticket--;
} else {
break;
}
}
}
}
}
线程安全3
/**
* 使用同步方法解决实现方式的线程安全问题。
*
* 1.默认的同步方法(非静态的)的锁是:当前对象,也就是this.
* 2.默认的同步方法(静态的)的锁是:当前类本身.
*
*/
public class WindowTest2 {
public static void main(String[] args) {
Window2 w = new Window2();
Thread t1 = new Thread(w);
Thread t2 = new Thread(w);
Thread t3 = new Thread(w);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
class Window2 implements Runnable{
int ticket = 100;
@Override
public void run() {
while(true){
show();
}
}
//同步方法:保证同一个时间段内,只能有一个线程来访问。
private synchronized void show() {
if(ticket > 0){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "售票,票号为:" + ticket);
ticket--;
}
}
}
线程安全4
/**
* 使用同步方法解决继承方式的线程安全问题
*
* 注意:继承的方式中,要慎用同步方法。
*
*/
public class WindowTest3 {
public static void main(String[] args) {
Window3 w1 = new Window3();
Window3 w2 = new Window3();
Window3 w3 = new Window3();
w1.setName("窗口1");
w2.setName("窗口2");
w3.setName("窗口3");
w1.start();
w2.start();
w3.start();
}
}
class Window3 extends Thread {
static int ticket = 100;
@Override
public void run() {
while (true) {
show();
}
}
// private synchronized void show() {//默认的锁:this:w1,w2,w3
private static synchronized void show() {//默认的锁:当前类本身:Window3.class,是唯一的。
if (ticket > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "售票,票号为:" + ticket);
ticket--;
}
}
}
练习
public class Customer extends Thread{
private Account account;
public Customer(Account account) {
this.account = account;
}
public Account getAccount() {
return account;
}
public void setAccount(Account account) {
this.account = account;
}
@Override
public void run() {
for(int i=0;i<3;i++){
account.deposit(1000);
}
}
}
public class Account {
private double balance;//账户余额
public double getBalance() {
return balance;
}
public void setBalance(double balance) {
this.balance = balance;
}
public Account() {
super();
}
public Account(double balance) {
super();
this.balance = balance;
}
/**
* 存钱的操作
* @param number
*/
public synchronized void deposit(double number) {
notify();
balance+=number;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"存款金额:"+ number +"余额为:"+balance);
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
/** * 银行有一个账户。 有两个储户分别向同一个账户存3000元,每次存1000,存3次。每次存完打印账户余额。
问题:该程序是否有安全问题,如果有,如何解决?
分析:1.确定是多线程的问题。
2.此时的多个线程:两个储户
3.此时的账户,是共享数据。保证账户的唯一性
4.是否存在线程的安全问题? 有共享数据,有安全问题。
5.考虑如何处理:使用同步机制。
* 拓展:如何实现两个储户交替存钱:线程的通信:wait() / notify() / notifyAll()
*/
public class ThreadTest {
public static void main(String[] args) {
Account account=new Account();
Customer customer1 = new Customer(account);
Customer customer2 = new Customer(account);
customer1.setName("用户1");
customer2.setName("用户2");
customer1.start();
customer2.start();
}
}
线程安全
/**
* 实现线程安全的懒汉式(单例模式)
*/
public class BankTest {
}
class Bank{
private Bank(){}
private static Bank bank = null;
//方式一:
// public static synchronized Bank getInstance(){//当前的同步监视器:Bank.class
//
// if(bank == null){
//
// bank = new Bank();
// }
//
// return bank;
// }
//方式二:相较于方式一,效率高一些
public static Bank getInstance(){
if(bank == null){
synchronized (Bank.class) {
if (bank == null) {
bank = new Bank();
}
}
}
return bank;
}
}
使用ReentrantLock线程安全
import java.util.concurrent.locks.ReentrantLock;
/**
*
* 模仿车站买票程序。开启3个窗口卖票。总票数为100张。-----使用实现的方式
*
* 解决线程安全问题的方式三:
* 存在线程的安全问题,使用Lock的方式解决线程安全问题。
*
*
* 面试题:同步的方式和Lock的方式,解决线程安全方面的异同?
* 同:解决了线程安全问题
* 异:同步的方式:同步监视器在线程执行完操作共享数据的代码以后,自动释放
* Lock的方式:需要显式的调用unlock()方法之后,才能保证其他线程操作共享数据。
*
*
*/
public class WindowTest {
public static void main(String[] args) {
Window w = new Window();
Thread t1 = new Thread(w);
Thread t2 = new Thread(w);
Thread t3 = new Thread(w);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
class Window implements Runnable{
int ticket = 100;
//1.ReentrantLock的实例化
private ReentrantLock lock = new ReentrantLock();//保证此对象的唯一性。
@Override
public void run() {
while(true){
try{
//2.调用lock()
lock.lock();
if(ticket > 0){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "售票,票号为:" + ticket);
ticket--;
}else{
break;
}
}finally{
//3.调用unlock()
lock.unlock();
}
}
}
}
/**
* 死锁:不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁
*
*
* 死锁,是我们开发中需要规避的!
*/
public class DeadLockTest {
public static void main(String[] args) {
StringBuffer s1 = new StringBuffer();
StringBuffer s2 = new StringBuffer();
new Thread(new Runnable() {
public void run() {
synchronized (s1) {
s1.append("a");// 类似s += "a"
s2.append("1");
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (s2) {
s1.append("b");
s2.append("2");
System.out.println(s1);
System.out.println(s2);
}
}
}
}).start();
new Thread(new Runnable() {
public void run() {
synchronized (s2) {
s1.append("c");// 类似s += "a"
s2.append("3");
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (s1) {
System.out.println("#########2###########");
s1.append("d");
s2.append("4");
System.out.println(s1);
System.out.println(s2);
}
}
}
}).start();
}
}
//死锁的问题
class A {
public synchronized void foo(B b) {
System.out.println("当前线程名: " + Thread.currentThread().getName()
+ " 进入了A实例的foo方法"); // ①
try {
Thread.sleep(200);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
System.out.println("当前线程名: " + Thread.currentThread().getName()
+ " 企图调用B实例的last方法"); // ③
b.last();
}
public synchronized void last() {
System.out.println("进入了A类的last方法内部");
}
}
class B {
public synchronized void bar(A a) {
System.out.println("当前线程名: " + Thread.currentThread().getName()
+ " 进入了B实例的bar方法"); // ②
try {
Thread.sleep(200);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
System.out.println("当前线程名: " + Thread.currentThread().getName()
+ " 企图调用A实例的last方法"); // ④
a.last();
}
public synchronized void last() {
System.out.println("进入了B类的last方法内部");
}
}
public class DeadLock implements Runnable {
A a = new A();
B b = new B();
public void init() {
Thread.currentThread().setName("主线程");
// 调用a对象的foo方法
a.foo(b);
System.out.println("进入了主线程之后");
}
public void run() {
Thread.currentThread().setName("副线程");
// 调用b对象的bar方法
b.bar(a);
System.out.println("进入了副线程之后");
}
public static void main(String[] args) {
DeadLock dl = new DeadLock();
dl.init();
new Thread(dl).start();
}
}
13-5 线程的通信
例 题
使用两个线程打印 1-100. 线程1, 线程2 交替打印
/**
* 线程的通信:
* wait():一个线程在执行过程中,一旦调用此方法,则此线程进入阻塞状态,等待其他线程来唤醒自己。
* notify():一个线程在执行过程中,一旦调用此方法,则会唤醒被wait()的一个线程。高优先级的要优先被唤醒。
* notifyAll():一个线程在执行过程中,一旦调用此方法,则会唤醒所有被wait()的线程。
*
*
*
* 例题:使用两个线程打印 1-100. 线程1, 线程2 交替打印
*
* 注意点:1.此三个方法必须使用在同步中。
* 2.此三个方法的调用者是同步监视器!否则,如果三个方法的调用者不是同步监视器,报异常。
* 3.此三个方法定义在Object类
*
* 面试题:sleep() 和 wait() 的异同?
* 1.方法声明在哪? Thread:sleep() Object:wait()
* 2.共同点:使得当前线程进入阻塞状态
* 3.使用的范围要求:sleep()使用没有情境的要求;wait()必须使用在同步代码块或同步方法中
* 4.都使用在同步当中的话:wait()需要唤醒:notify()/notifyAll(); sleep():不会释放锁;wait()会释放锁
*
*/
class Num implements Runnable{
int number = 1;
Object obj = new Object();
@Override
public void run() {
while(true){
synchronized (obj) {
obj.notify();
if (number <= 100) {
try {
Thread.sleep(10);////一旦执行此方法,当前线程进入阻塞状态,不释放同步监视器。
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":" + number);
number++;
//一旦执行此方法,当前线程进入阻塞状态,释放同步监视器。
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
break;
}
}
}
}
}
public class CommunicationTest {
public static void main(String[] args) {
Num n = new Num();
Thread t1 = new Thread(n);
Thread t2 = new Thread(n);
t1.setName("线程1");
t2.setName("线程2");
t1.start();
t2.start();
}
}
/**
* 多线程的应用:生产者、消费者问题
*
* 生产者(Producer)将产品交给店员(Clerk),而消费者(Customer)从店员处取走产品,
* 店员一次只能持有固定数量的产品(比如:20),如果生产者试图生产更多的产品,店员会叫生产者停一下,
* 如果店中有空位放产品了再通知生产者继续生产;如果店中没有产品了,店员会告诉消费者等一下,
* 如果店中有产品了再通知消费者来取走产品。
*
*
* 分析:
* 1.是不是多线程问题?是。此时的生产者、消费者就是线程
* 2.是否存在线程安全问题? 是,因为有共享数据
* 3.共享数据是? 产品(或产品的数量)
* 4.使用同步机制处理线程的安全问题:操作共享数据的代码 ; 使用唯一的同步监视器
* 5.涉及线程的通信问题。
*/
class Producer extends Thread{//生产者
private Clerk clerk;
public Producer(Clerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
System.out.println("生产者开始生产产品....");
//不停的生产产品
while(true){
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
clerk.addProduct();
}
}
}
class Consumer extends Thread{//消费者
private Clerk clerk;
public Consumer(Clerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
System.out.println("消费者开始消费产品....");
//不停的消费产品
while(true){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
clerk.reduceProduct();
}
}
}
class Clerk{//店员
private int product = 0;//产品的数量
public synchronized void reduceProduct() {//消费产品
if(product > 0){
System.out.println(Thread.currentThread().getName() + "消费了第" + product + "个产品");
product--;
notifyAll();//将生产者唤醒
}else{
//没有产品了,应该等待。
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public synchronized void addProduct() {//生产产品
if(product < 20){
product++;
System.out.println(Thread.currentThread().getName() + "生产了第" + product + "个产品");
notifyAll();//将消费者唤醒
}else{
//已经达到上限20,等待
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class ProducerAndConsumer {
public static void main(String[] args) {
Clerk clerk = new Clerk();
Consumer consumer1 = new Consumer(clerk);
Consumer consumer2 = new Consumer(clerk);
Producer producer1 = new Producer(clerk);
consumer1.setName("消费者1");
consumer2.setName("消费者2");
producer1.setName("生产者1");
consumer1.start();
consumer2.start();
producer1.start();
}
}
13-6 线程池
系统启动一个新线程的成本是比较高的,因为它涉及与os交互。这种情况下,系统启动时即创建大量空闲的线程,就可以很好地提高性能,尤其是当程序需要创建大量生存期很短暂的线程时。
除此之外,使用线程池可以有效地控制系统中并发线程的数量。避免因并发创建的线程过多,导致系统性能下降,JVM崩溃。
Java 5以前,需要手动创建自己的线程池;Java 5开始,新增了Executors工厂类产生线程池。
使用线程池执行线程任务的步骤如下:
- 调用Executors 类的静态方法newFixedThreadPool(int nThreads),创建一个可重用的、具有固定线程数的线程池ExecutorService对象
- 创建Runnable实例,作为线程执行任务
- 调用ExecutorService对象的submit()提交Runnable实例
- 调用ExecutorService对象的shutDown()方法关闭线程池。
您的资助是我最大的动力!
金额随意,欢迎来赏!