(一)多线程概念
这句话很重要:要想实现多线程,必须在主线程中创建新的线程对象。
1.1并行与并发的区别
并行是多个任务在同一时刻内发生,并发是多个任务在同一时间间隔内发生。
(二)多线程实现
2.1多线程的创建
2.1.1继承于Thread类
多线程的创建,方式一:继承于Thread类
- 创建一个继承于Thread类的子类
- 重写Thread类的run() --> 将此线程执行的操作声明在run()中
- 创建Thread类的子类的对象
- 通过此对象调用start()
class MyThread extends Thread{
@Override
public void run() {
for(int i=0;i<100;i++){
if(i%2==0){
System.out.println(Thread.currentThread().getName()+i);
}
}
}
}
public class ThreadFirst {
// * 多线程的创建,方式一:继承于Thread类
// * 1. 创建一个继承于Thread类的子类
// * 2. 重写Thread类的run() --> 将此线程执行的操作声明在run()中
// * 3. 创建Thread类的子类的对象
// * 4. 通过此对象调用start()
public static void main(String args[]) {
MyThread T1 = new MyThread();
T1.start();
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
System.out.println(Thread.currentThread().getName() + i);
}
}
}
}
2.1.2实现Runnable接口
方法二:实现Runnable接口
步骤与上面相似,但是不能直接创建线程对象调用start方法。
package com.sgyj.java;
class thread01 implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"你好");
}
}
public class ThreadTest1 {
public static void main(String args[]){
thread01 t1=new thread01();
Thread t2=new Thread(t1);
//调用的是thread的run方法,调用的是runnable类型的target的run 即t1的run
t2.start();
System.out.println(Thread.currentThread().getName()+"mooo");
}
}
两种方法的对比
比较创建线程的两种方式。
-
开发中:优先选择:实现Runnable接口的方式
-
原因:1. 实现的方式没有类的单继承性的局限性
2. 实现的方式更适合来处理多个线程有共享数据的情况。 -
联系:public class Thread implements Runnable
-
相同点:两种方式都需要重写run(),将线程要执行的逻辑声明在run()中。
2.1.3实现Callable
步骤:
1、创建一个实现Callable的实现类
2、实现call方法,将此线程需要执行的操作声明在call()中
3、创建Callable接口实现类的对象
4、将此Callable接口实现类的对象作为传递到FutureTask构造器中,创建FutureTask的对象
5、将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start()
6、获取Callable中call方法的返回值。get()返回值即为FutureTask构造器参数Callable实现类重写的call()的返回值。
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/**
* 创建线程的方式三:实现Callable接口。 --- JDK 5.0新增
*
*
* 如何理解实现Callable接口的方式创建多线程比实现Runnable接口创建多线程方式强大?
* 1. call()可以有返回值的。
* 2. call()可以抛出异常,被外面的操作捕获,获取异常的信息
* 3. Callable是支持泛型的
*
* @author shkstart
* @create 2019-02-15 下午 6:01
*/
//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;
}
}
public class ThreadNew {
public static void main(String[] args) {
//3.创建Callable接口实现类的对象
NumThread numThread = new NumThread();
//4.将此Callable接口实现类的对象作为传递到FutureTask构造器中,创建FutureTask的对象
FutureTask futureTask = new FutureTask(numThread);
//5.将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start()
new Thread(futureTask).start();
try {
//6.获取Callable中call方法的返回值
//get()返回值即为FutureTask构造器参数Callable实现类重写的call()的返回值。
Object sum = futureTask.get();
System.out.println("总和为:" + sum);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
实现Callable有什么好处:
1、call()可以有返回值的。
2、call()可以抛出异常,被外面的操作捕获,获取异常的信息
3、Callable是支持泛型的
2.1.4使用线程池
好处:
1.提高响应速度(减少了创建新线程的时间)
2.降低资源消耗(重复利用线程池中线程,不需要每次都创建)
3.便于线程管理
corePoolSize:核心池的大小
maximumPoolSize:最大线程数
keepAliveTime:线程没有任务时最多保持多长时间后会终止
package com.atguigu.java2;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
/**
* 创建线程的方式四:使用线程池
*
* 好处:
* 1.提高响应速度(减少了创建新线程的时间)
* 2.降低资源消耗(重复利用线程池中线程,不需要每次都创建)
* 3.便于线程管理
* corePoolSize:核心池的大小
* maximumPoolSize:最大线程数
* keepAliveTime:线程没有任务时最多保持多长时间后会终止
*
*
* 面试题:创建多线程有几种方式?四种!
* @author shkstart
* @create 2019-02-15 下午 6:30
*/
class NumberThread implements Runnable{
@Override
public void run() {
for(int i = 0;i <= 100;i++){
if(i % 2 == 0){
System.out.println(Thread.currentThread().getName() + ": " + i);
}
}
}
}
class NumberThread1 implements Runnable{
@Override
public void run() {
for(int i = 0;i <= 100;i++){
if(i % 2 != 0){
System.out.println(Thread.currentThread().getName() + ": " + i);
}
}
}
}
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.execute(new NumberThread1());//适合适用于Runnable
// service.submit(Callable callable);//适合使用于Callable
//3.关闭连接池
service.shutdown();
}
}
2.2多线程的常用方法
测试Thread中的常用方法:
- start():启动当前线程;调用当前线程的run()
- run(): 通常需要重写Thread类中的此方法,将创建的线程要执行的操作声明在此方法中
- currentThread():静态方法,返回执行当前代码的线程
- getName():获取当前线程的名字
- setName():设置当前线程的名字
- yield():释放当前cpu的执行权
- join():在线程a中调用线程b的join(),此时线程a就进入阻塞状态,直到线程b完全执行完以后,线程a才结束阻塞状态。
- stop():已过时。当执行此方法时,强制结束当前线程。
- sleep(long millitime):让当前线程“睡眠”指定的millitime毫秒。在指定的millitime毫秒时间内,当前线程是阻塞状态。
- isAlive():判断当前线程是否存活
方法面试题
sleep()与wait()的异同
相同点:都能让线程进入阻塞状态
不同点:
声明位置不同:Thread类中声明sleep(),object类中声明wait()
调用的要求不用:sleep()可以在任何场景下调用,wait()只能在同步代码快中调用
同步监视器的释放:两个方法如果都定义在同步代码快或者同步方法中,sleep不释放锁,wait会释放锁。
2.3线程的优先级
MAX_PRIORITY :10
MIN _PRIORITY :1
NORM_PRIORITY :5
涉及的方法
getPriority() :返回线程优先值
setPriority(int newPriority) :改变线程的优先级\
(三)线程的生命周期
操作系统得学好!
(四)线程的安全问题(同步与互斥)
4.1使用(synchronized)代码块
语法:
synchronized(同步监视器){
需要被同步的代码
}
说明:
1.操作共享数据的代码,即为需要被同步的代码。 -->不能包含代码多了,也不能包含代码少了。
2.共享数据:多个线程共同操作的变量。比如:ticket就是共享数据。
3.同步监视器,俗称:锁。任何一个类的对象,都可以充当锁。
要求:多个线程必须要共用同一把锁。
补充:在实现Runnable接口创建多线程的方式中,我们可以考虑使用this充当同步监视器。
4.同步的方式,解决了线程的安全问题。—好处
操作同步代码时,只能有一个线程参与,其他线程等待。相当于是一个单线程的过程,效率低。 —局限性
注意:多个线程必须要共用同一把锁。
1、这点比较重要,我在下面有两个程序一个继承Thread,一个实现Runnable ,分别打印了obj锁的内容。
2、解决办法:
实现Runnable 中可以把锁写成this对象继承
继承Thread可以把锁变加static或者写为(当前类.class)
//这个共用同一把锁
class windows implements Runnable{
private int ticket=100;
Object obj=new Object();
@Override
public void run() {
System.out.println(obj);
//Object obj=new Object();
while(true){
synchronized (obj){
if(ticket>0){
//给其他线程一个机会
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":"+"卖票,票号为:"+ticket);
ticket--;
}else {
break;
}
}
}
}
}
public class ThreadSynchronized {
public static void main(String[] args) {
windows w1=new windows();
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();
}
}
//输出
java.lang.Object@1f38108d
java.lang.Object@1f38108d
java.lang.Object@1f38108d
//这分别创建三个锁
class windowsT extends Thread{
private static int ticket=100;
Object obj=new Object();
@Override
public void run() {
System.out.println(obj);
//Object obj=new Object();
while(true){
synchronized (obj){
if(ticket>0){
//给其他线程一个机会
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":"+"卖票,票号为:"+ticket);
ticket--;
}else {
break;
}
}
}
}
}
public class ThreadSynchronizedT {
public static void main(String[] args) {
//三个对象 使用三把锁,每个对象都创建了一个obj
windowsT w1=new windowsT();
windowsT w2=new windowsT();
windowsT w3=new windowsT();
// Thread t1=new Thread(w1);
// Thread t2=new Thread(w1);
// Thread t3=new Thread(w1);
w1.setName("售票处1");
w2.setName("售票处2");
w3.setName("售票处3");
w1.start();
w2.start();
w3.start();
System.out.println();
}
}
//输出,三个锁不一样
java.lang.Object@740759f
java.lang.Object@c7618ab
java.lang.Object@6857aa8a
//可以把锁设置为static 解决继承锁不统一
private static Object obj=new Object();
4.2使用同步方法
在方法前加入Synchronized关键字
1、实现Runnable实现同步方法解决安全问题
class Window3 implements Runnable {
private int ticket = 100;
@Override
public void run() {
while (true) {
show();
}
}
private synchronized void show(){
//同步监视器:this
//synchronized (this){
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":卖票,票号为:" + ticket);
ticket--;
}
//}
}
}
public class WindowTest3 {
public static void main(String[] args) {
Window3 w = new Window3();
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();
}
}
2、继承Thread实现同步方法解决安全问题
class Window4 extends Thread {
private static int ticket = 100;
@Override
public void run() {
while (true) {
show();
}
}
private static synchronized void show(){
//同步监视器:Window4.class
//private synchronized void show(){ //同步监视器:t1,t2,t3。此种解决方式是错误的
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":卖票,票号为:" + ticket);
ticket--;
}
}
}
public class WindowTest4 {
public static void main(String[] args) {
Window4 t1 = new Window4();
Window4 t2 = new Window4();
Window4 t3 = new Window4();
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
总结:
- 同步方法仍然涉及到同步监视器,只是不需要我们显式的声明。
- 非静态的同步方法,同步监视器是:this
静态的同步方法,同步监视器是:当前类本身
4.3 使用lock解决线程安全问题
1、从JDK 5.0开始,Java提供了更强大的线程同步机制——通过显式定义同
步锁对象来实现同步。同步锁使用Lock对象充当。
2、java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的
工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象
加锁,线程开始访问共享资源之前应先获得Lock对象。
3、ReentrantLock 类实现了 Lock ,它拥有与 synchronized 相同的并发性和
内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以
显式加锁、释放锁
class Window 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();
}
}
}
}
public class LockTest {
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();
}
}
注意:如果同步代码块有异常 ,需要用try finally包裹。 finally中是释放锁的代码,一定要记得释放
面试题:synchronized 与 Lock的异同?
相同:二者都可以解决线程安全问题
不同:synchronized机制在执行完相应的同步代码以后,自动的释放同步监视器
Lock需要手动的启动同步(lock()),同时结束同步也需要手动的实现(unlock())
(五)死锁
5.1什么时候会释放锁
- 当前线程的同步方法、同步代码块执行结束。
- 当前线程在同步代码块、同步方法中遇到break、return终止了该代码块、该方法的继续执行。
- 当前线程在同步代码块、同步方法中出现了未处理的Error或Exception,导
致异常结束。 - 当前线程在同步代码块、同步方法中执行了线程对象的wait()方法,当前线
程暂停,并释放锁
不会释放锁的操作
- 线程执行同步代码块或同步方法时,程序调用Thread.sleep()、Thread.yield()方法暂停当前线程的执行
- 线程执行同步代码块时,其他线程调用了该线程的suspend()方法将该线程
挂起,该线程不会释放锁(同步监视器)。
5.2死锁事例
package com.sgyj.java;
public class DeadLock {
//死锁问题
public static void main(String[] args) {
final StringBuffer s1 = new StringBuffer();
final StringBuffer s2 = new StringBuffer();
new Thread() {
public void run() {
synchronized (s1) {
s2.append("A");
synchronized (s2) {
s2.append("B");
System.out.print(s1);
System.out.print(s2);
}
}
}
}.start();
new Thread() {
public void run() {
synchronized (s2) {
s2.append("C");
synchronized (s1) {
s1.append("D");
System.out.print(s2);
System.out.print(s1);
}
}
}
}.start();
}
}
说明:当以下情况发生时,发生死锁:
线程1拿到s1锁,线程2拿到s2锁,由于同步代码块中的程序还未执行完,两个锁未释放
线程1要拿到s2锁执行剩下代码,线程2要拿到s1锁执行剩下代码
此时两个线程都在等待对方释放锁来执行剩下代码,程序陷入死锁。
(六)线程的通信
6.1线程通信的三个方法
- wait():一旦执行此方法,当前线程就会进入阻塞状态
- notify():一旦执行此方法,就会唤醒被wait的一个线程。如果有多个线程被wait,就唤醒优先级高的那个。
- notifyAll():一旦执行此方法,就会唤醒所有被wait的线程。
在线程通信中:
1、wait(),notify(),notifyAll()三个方法都需要在同步代码块或者同步方法中,不能使用lock
2、wait(),notify(),notifyAll()三个方法的调用者必须是同步代码块或者同步方法中的同步监视器
3、wait(),notify(),notifyAll()三个方法是定义在java.lang.Object类中。
6.1线程通信例子
使用两个线程打印 1-100。线程1, 线程2 交替打印
class Number implements Runnable{
private int number = 1;
private 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 {
//使得调用如下wait()方法的线程进入阻塞状态
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else{
break;
}
}
}
}
}
public class CommunicationTest {
public static void main(String[] args) {
Number number = new Number();
Thread t1 = new Thread(number);
Thread t2 = new Thread(number);
t1.setName("线程1");
t2.setName("线程2");
t1.start();
t2.start();
}
}
6.2生产者消费者问题
package com.sgyj.java;
class Clerk{
private int product=0;
public Clerk(int product) {
this.product = product;
}
public synchronized void get(){
if(this.product>0){
System.out.println(Thread.currentThread().getName()+":拿走了第"+product+"个产品");
product--;
notify();
}else{
System.out.println("请您等一下,正在做");
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public synchronized void put(){
if(this.product<20){
product++;
System.out.println(Thread.currentThread().getName()+":开始生产了第"+product+"个产品");
notify();
}else{
System.out.println("商品已经放不下了,请稍等在做");
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class Producer extends Thread {
private Clerk clerk;
public Producer(Clerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
while (true) {
try {
sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
clerk.put();
}
}
}
class Customerr extends Thread {
private Clerk clerk;
public Customerr(Clerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
while (true) {
try {
sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
clerk.get();
}
}
}
public class Product {
public static void main(String[] args) {
Clerk clerk = new Clerk(10);
Producer p = new Producer(clerk);
Customerr c = new Customerr(clerk);
p.setName("生产者");
c.setName("消费者");
p.start();
c.start();
}
}