(一)多线程概念
这句话很重要:要想实现多线程,必须在主线程中创建新的线程对象。
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 {
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);
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;
class NumThread implements Callable{
@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) {
NumThread numThread = new NumThread();
FutureTask futureTask = new FutureTask(numThread);
new Thread(futureTask).start();
try {
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;
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) {
ExecutorService service = Executors.newFixedThreadPool(10);
ThreadPoolExecutor service1 = (ThreadPoolExecutor) service;
service.execute(new NumberThread());
service.execute(new NumberThread1());
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);
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);
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) {
windowsT w1=new windowsT();
windowsT w2=new windowsT();
windowsT w3=new windowsT();
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
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(){
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(){
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;
private ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while(true){
try{
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 {
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 {
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();
}
}
|