8.1 基本概念:程序,进程,线程
每个线程都有独立的栈,程序计数器
多个线程共享同一个进程中的结构:方法区,堆
一个Java应用程序java.exe,其实至少有三个线程: main()主线程, gc()垃圾回收线程,异常处理线程。当然如果发生异常,会影响主线程。
单核CPU(假的多线程,一个时间单元内只能执行一个线程任务)与多核CPU
并行(多个CPU同时执行多个任务,多个人做不同的事)与并发(一个CPU同时执行多个任务,秒杀,多个人做同一件事)
8.2 线程的创建和使用
多线程的创建方式一:继承于Thread类
public class ThreadTest {
public static void main(String[] args) {
//3.创建Thread类的子类的对象
Mythread t1 = new Mythread();
//4.通过此对象调用start():启动当前线程;调用当前线程的run()
t1.start();
System.out.println("hello");
//t1.run();不能通过直接调用run的方式(普通方法)启动线程
//t1.start();//再启动一个线程,不可以再让以及start的线程去执行,会报错IllegalThreadStateException
//创建一个新的线程对象
Mythread t2 = new Mythread();
t2.start();
//创建Thread类的匿名子类的方式
new Thread(){
@Override
public void run() {
for (int i = 0; i <100 ; i++) {
if(i % 2!=0) {
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
}.start();
}
}
//1.创建一个继承于Thread类的子类
class Mythread extends Thread{
//2.重写Thread类的run()
@Override
public void run() {
for (int i = 0; i <100 ; i++) {
if(i % 2==0) {
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
}
Thread类的有关方法
1.start():启动当前线程:调用当前线程的run()
2.run():通过需要重写的Thread类中的此方法,将创建的线程要执行的操作声明在此方法中
3.currentThread():静态方法,返回执行当前代码的线程
4.getName():获取当前线程的名字
5.setName():设置当前线程的名字
6.yeild():释放当前cpu的执行权
7.join():在线程a中调用线程b的join,此时线程a就进入阻塞状态,知道线程b完全执行完后,线程a 才结束阻塞状态
8.stop():已过时。当执行此方法时,强制结束当前线程
9.sleep(long millitime):让当前线程“睡眠”指定的millitime毫秒,在指定的millitime毫秒时间内,当前线程是阻塞状态。
10.isAlive():判断当前线程是否存活
线程的调度
同优先级线程组成先进先出队列(先到先服务),使用时间片策略
对高优先级,使用优先调度的抢占式策略
线程的优先级
public class ThreadMethodTest {
public static void main(String[] args) {
HelloThread h1 = new HelloThread();
h1.setName("线程一");
h1.setPriority(Thread.MAX_PRIORITY);//概率上,优先级高被优先执行的概率高
//不意味着高优先级的线程执行完,低优先级的线程才执行
h1.start();
//给主线程命名
Thread.currentThread().setName("主线程");
Thread.currentThread().setPriority(Thread.MIN_PRIORITY);
for (int i = 0; i <100 ; i++) {
if(i%2==0){
System.out.println(Thread.currentThread().getName()+":"+i);
}
if(i==20){
try {
h1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
System.out.println(h1.isAlive());//false
}
}
class HelloThread extends Thread{
@Override
public void run() {
for (int i = 0; i <100 ; i++) {
if(i % 2==0) {
// try {//不能throws异常,因为父类没有抛异常
// //子类重写异常不能大于父类
// sleep(1000);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
//System.out.println(Thread.currentThread().getName()+":"+i);
System.out.println(getName()+":"+i);
}
// if(i==20){
// yield();
// }
}
}
}
多线程的创建方式二:实现Runnable接口
public class ThreadTest1 {
public static void main(String[] args) {
//3.创建实现类的对象
Mythread1 m1 = new Mythread1();
//4.将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
Thread t1=new Thread(m1);
//5.通过Thread类的对象调用start:启动线程;调用当前线程的run--->调用了Runable类型的run
t1.start();
//再启动一个线程
Thread t2=new Thread(m1);
t2.start();
}
}
//1.定义子类,实现Runnable接口
class Mythread1 implements Runnable{
//2.实现类去实现Runnable中的抽象方法
@Override
public void run() {
for (int i = 0; i <100 ; i++) {
if(i % 2==0) {
System.out.println(Thread.currentThread().getName()+":"+i);
//不能直接getname
}
}
}
}
比较创建线程的两种方式
开发中:优先选择:实现Runable接口的方式
原因:1.实现的方式没有类的单继承性的局限性
2.实现的方式更适合来处理多个线程有共享数据的情况
联系:public class Thread implements Runable
相同点:两种方式都需要重写run,将线程要执行的逻辑声明在run中
8.3 线程的生命周期
关注两个概念:状态,相应的方法
关注:状态a----状态b:哪些方法执行了(回调方法)
某个方法的主动调用:状态a----状态b
阻塞为临时状态,死亡为最终状态
8.4 线程的同步
模拟火车站售票程序,开启三个窗口售票。
不安全问题:sleep的方法,加大了三个窗口同时抢得一张票的概率,即重票错票问题。问题出现原因:当某个线程操作车票过程中,尚未操作完成时,其他线程也参与进来,操作车票。
public class Windowclass1 {
public static void main(String[] args) {
Window1 w1 = new Window1();
Thread t1 = new Thread(w1);
Thread t2 = new Thread(w1);
Thread t3 = new Thread(w1);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
class Window1 implements Runnable{
private int ticket=100;
@Override
public void run() {
while(true){
if(ticket>0){
try {
Thread.sleep(100);//静态方法
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":卖票,票号为:"+ticket);
ticket--;
}else{
break;
}
}
}
}
方式一:同步代码块
synchronized(同步监视器){
//需要被同步的代码
}
说明:
1.操作共享数据的代码,即为需要被同步的代码
2.共享数据:多个线程共同操作的变量。比如:ticket
3.同步监视器,俗称:锁。任何一个类的对象,都可以充当锁
要求:多个线程必须要共用同一把锁
4.在实现Runnable接口创建多线程的方式中,我们可以考虑使用this充当同步监视器。
public class Windowclass {
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{
private static int ticket=100;
private static Object obj=new Object();
@Override
public void run() {
while(true){
//synchronized (obj){
//慎用this充当同步监视器,考虑使用当前类充当同步监视器
//synchronized (this){//不唯一,w1,w2,w3三个对象
synchronized (Window.class){
//Class class=Window.class,只会加载一次
if(ticket>0){
System.out.println(getName()+":卖票,票号为:"+ticket);
ticket--;
}else{
break;
}
}
}
}
}
public class Windowclass1 {
public static void main(String[] args) {
Window1 w1 = new Window1();
Thread t1 = new Thread(w1);
Thread t2 = new Thread(w1);
Thread t3 = new Thread(w1);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
class Window1 implements Runnable{
private int ticket=100;
Object obj=new Object();
@Override
public void run() {
while(true){
//synchronized (obj){
synchronized (this){//此时的this:唯一的window1的对象
if(ticket>0){
try {
Thread.sleep(100);//静态方法
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":卖票,票号为:"+ticket);
ticket--;
}else{
break;
}
}
}
}
}
方式二:同步方法
1.同步方法仍然涉及到同步监视器,只是不需要我们显示的声明
2.非静态的同步方法:同步监视器:this
3.静态的同步方法,同步监视器是:当前类本身
public class Windowclass1 {
public static void main(String[] args) {
Window1 w1 = new Window1();
Thread t1 = new Thread(w1);
Thread t2 = new Thread(w1);
Thread t3 = new Thread(w1);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
class Window1 implements Runnable{
private int ticket=100;
Object obj=new Object();
@Override
public void run() {
while(ticket>0){
show();
}
}
public synchronized void show(){//同步监视器this
if(ticket>0){
try {
Thread.sleep(100);//静态方法
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":卖票,票号为:"+ticket);
ticket--;
}
}
//public synchronized void run() {
//synchronized(this){一个窗口一直取票
// while(true){
// if(ticket>0){
// try {
// Thread.sleep(100);//静态方法
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// System.out.println(Thread.currentThread().getName()+":卖票,票号为:"+ticket);
// ticket--;
// }else{
// break;
// }
// }
// }
}
public class Windowclass {
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{
private static int ticket=100;
private static Object obj=new Object();
@Override
public void run() {
while(ticket>0){
show();
}
}
public static synchronized void show(){//同步监视器Window.class
//public synchronized void show(){//同步监视器this:w1,w2,w3
if(ticket>0){
try {
Thread.sleep(100);//静态方法
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":卖票,票号为:"+ticket);
ticket--;
}
}
}
单例模式懒汉式
class Bank{
private Bank(){}
private static Bank instance=null;
public static synchronized Bank getInstance(){
if(instance==null){
instance=new Bank();
}
return instance;
}
}
class Bank{
private Bank(){}
private static Bank instance=null;
public static Bank getInstance(){
//方式一:效率稍差
// synchronized (Bank.class) {
// if(instance==null){
// instance=new Bank();
// }
// return instance;
// }
//方式二 效率稍高,后面不为null的对象直接返回,不需要等待
if(instance==null){
synchronized (Bank.class) {
if (instance == null) {
instance = new Bank();
}
}
}
return instance;
}
}
死锁问题
不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃
自己需要的同步资源,就形成了线程的死锁
出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于
阻塞状态,无法继续
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();
new Thread(dl).start();
dl.init();
}
}
方式三:Lock锁
面试题:synchronized与Lock的区别
相同:二者都可以解决线性安全问题
不同:synchronized机制在执行完相应的同步代码以后,自动释放同步监视器
Lock需要手动的启动同步(Lock())。同时结束同步也需要手动的实现(unlock())
public class WindowTest2 {
public static void main(String[] args) {
Window2 w1 = new Window2();
Thread t1 = new Thread(w1);
Thread t2 = new Thread(w1);
Thread t3 = new Thread(w1);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
class Window2 implements Runnable {
private 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(100);//静态方法
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":卖票,票号为:" + ticket);
ticket--;
}else{ break;}
} finally {
//3.调用解锁方法unlock()
lock.unlock();
}
}
}
}
练习1
银行有一个账户。有两个储户分别向同一个账户存3000元, 每次存1000, 存3次。每次存完打印账户余额。
public class AccountTest {
public static void main(String[] args) {
Account acc = new Account();
Customer c1 = new Customer(acc);
Customer c2 = new Customer(acc);
c1.setName("储户1");
c2.setName("储户2");
c1.start();
c2.start();
}
}
class Account{
private static double balance=0;
public void deposit(double amount){
synchronized (this) {//同一个账户,可以使this
balance+=amount;
System.out.println(Thread.currentThread().getName()+":存款,余额为:"+getBalance());
}
}
public double getBalance() {
return balance;
}
}
class Customer extends Thread{
private static Account account;
public Customer(Account account){
this.account=account;
}
@Override
public void run() {
for (int i = 0; i < 3; i++) {
account.deposit(1000);
}
}
}
8.5 线程的通信
线程通信涉及到的三个方法:
wait():一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器。
notify():一旦执行此方法,就会唤醒被wait的一个线程。如果有多个线程被wait,就唤醒优先级高的那个。
notifyAll():一旦执行此方法,就会唤醒所有被wait的线程。
说明:
1.wait(),notify(),notifyAll()三个方法必须使用在同步代码块或同步方法中。
2.wait(),notify(),notifyAll()三个方法的调用者必须是同步代码块或同步方法中的同步监视器。否则,会出现IllegalMonitorStateException异常
3.wait(),notify(),notifyAll()三个方法是定义在java.lang.Object类中。
面试题:sleep() 和 wait()的异同?
1.相同点:一旦执行方法,都可以使得当前的线程进入阻塞状态。
2.不同点:1)两个方法声明的位置不同:Thread类中声明sleep() , Object类中声明wait()
2)调用的要求不同:sleep()可以在任何需要的场景下调用。 wait()必须使用在同步代码块或同步方法中
3)关于是否释放同步监视器:如果两个方法都使用在同步代码块或同步方法中,sleep()不会释放锁,wait()会释放锁。
**例子:**使用两个线程打印 1-100。线程1, 线程2 交替打印
public class NumberTest {
public static void main(String[] args) {
Number number = new Number();
Thread t1 = new Thread(number);
Thread t2 = new Thread(number);
t1.start();
t2.start();
}
}
class Number implements Runnable{
private int num=0;
private Object obj=new Object();
@Override
public void run() {
while (true) {
//synchronized (this) {
// notify(); //this.notify();
synchronized (obj) {
// this.notify(); error
obj.notify();
if(num<=100){
System.out.println(Thread.currentThread().getName()+":"+num);
num++;
}else{break;}
try {
//wait();//this.wait();
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
public abstract class ProductTest {
public static void main(String[] args) {
Clerk clerk=new Clerk();
Customer c1=new Customer(clerk);
Productor p1=new Productor(clerk);
Thread t1=new Thread(p1);
Thread t2=new Thread(c1);
Thread t3=new Thread(c1);
t1.start();
t2.start();
t3.start();
}
}
class Clerk {
private int num=0;
public int getNum() {
return num;
}
public synchronized void product(){
if(num<20){
num++;
System.out.println(Thread.currentThread().getName()+":生产第"+num+"个产品");
notify();
}else{
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public synchronized void consume(){
if(num>0){
System.out.println(Thread.currentThread().getName()+":,消耗第"+num+"个产品");
num--;
notify();
}else{
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class Productor implements Runnable{
private Clerk clerk=new Clerk();
public Productor(Clerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
while(true){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
clerk.product();
}
}
}
class Customer implements Runnable{
private Clerk clerk=new Clerk();
public Customer(Clerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
while(true){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
clerk.consume();
}
}
}
8.6 JDK5.0新增线程创建方式
创建线程的方式三:实现Callable接口-----JDK5.0新增
如何理解实现Callable接口的方式创建多线程比实现Runnable接口创建多线程方式强大?
call()可以有返回值的。
call()可以抛出异常,被外面的操作捕获,获取异常的信息
Callable是支持泛型的
public class ThreadNew {
public static void main(String[] args) {
//3.创建callable接口实现类的对象
NumThread numThread = new NumThread();
//4.将此callbale接口实现类的对象传递到FutureTask构造器中,创建FutureTask对象
FutureTask futureTask=new FutureTask(numThread);
//5.将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start
new Thread(futureTask).start();
try {
//get()返回值即为FutureTask构造器参数Callable实现类重写的call的返回值
Object sum=futureTask.get();
System.out.println(sum);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
//1.创建一个实现callable的实现类
class NumThread implements Callable {
//2.实现call方法,将此线程需要知悉的操作声明在call中
@Override
public Object call() throws Exception {
int sum=0;
for (int i = 1; i <=100 ; i++) {
if(i%2==0){
System.out.println(i);
sum+=i;
}
}
return sum;
}
}
创建线程的方式四:使用线程池-----JDK5.0新增
思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁、实现重复利用。类似生活中的公共交
通工具。
好处:
1.提高响应速度(减少了创建新线程的时间)
2.降低资源消耗(重复利用线程池中线程,不需要每次都创建)
3.便于线程管理
corePoolSize:核心池的大小
maximumPoolSize:最大线程数
keepAliveTime:线程没有任务时最多保持多长时间后会终止
面试题:创建多线程有几种方式?四种!
public class ThreadPool {
public static void main(String[] args) {
//1.提高指定线程数量的线程池
ExecutorService service= Executors.newFixedThreadPool(10);
ThreadPoolExecutor service1=(ThreadPoolExecutor)service;
//设置线程池的属性
// System.out.println(service.getClass());
// service1.setCorePoolSize(15);
// service1.setKeepAliveTime();
//2.执行指定的线程的操作,需要提供实现Runnable接口或Callable接口实现类的对象
service.execute(new NumberThread());//适合适用于Runnable
//service.submit(Callable callable);//适合适用于Callable
//关闭连接池
service.shutdown();
}
}
class NumberThread implements Runnable{
@Override
public void run() {
for (int i = 0; i <=100 ; i++) {
if(i%2==0){
System.out.println(i);
}
}
}
}