多线程深入理解
一:线程概述
1,线程和进程
进程:正在运行的程序,是系统进行资源分配和调用的独立单位。 每一个进程都有它自己的内存空间和系统资源。
线程:进程的执行单元,执行路径。
2,多线程和多进程的意义
多进程:提高CPU的使用率
多线程: 提高应用程序的使用率
3,线程的生命周期

二:实现多线程
实现方式一;编写一个类继承Thread类,,重写run()方法
public class MyThread extends Thread {
@Override
public void run() {
for (int x = 0; x < 200; x++) {
System.out.println(x);
}
}
}
public class MyThreadDemo {
public static void main(String[] args) {
MyThread my1 = new MyThread();
MyThread my2 = new MyThread();
my1.start();
my2.start();
}
实现方式二:编写一个类实现Runnable接口;重写run()方法;创建MyRunnable对象;创建Thread类对象,将MyRunnable对象作为形参进行传递
public class RunnableDemo {
public static void main(String[] args) {
MyRunnable mr1 = new MyRunnable();
Thread t1 = new Thread (mr1);
Thread t2 = new Thread (mr1);
Thread t1 = new Thread (mr1,"线程一");
Thread t2 = new Thread (mr1,"线程二");
t1.start();
t2.start();
}
}
class MyRunnable implements Runnable{
public void run(){
System.out.println(Thread.currentThread().getName()+":"+"yangjinbiao");
}
}
实现方式三: 实现Callable 接口和使用FutureTask接收返回值
Callable方式依赖线程池,线程的的submit方法可以接收一个Callable接口的实现对象,同时放回一个FutureTask,如果Callable的call方法出错,将会抛出一个异常。
首先查看API
Interface Callable<V>
发现这个接口有个泛型,在查看接口内的方法:
V call() 计算一个结果,如果不能这样做,就会抛出一个异常。
发现接口方法的返回值有个泛型值,可以得出该接口的泛型是call()的放回值。在通过API查看FutureTask类。
Class FutureTask<V>
这个类泛型也是接收Callable接口的call()方法的放回值,通过查看FutureTask类的方法。
V get() 等待计算完成,然后检索其结果。
protected void set(V v) 将此未来的结果设置为给定值,除非此未来已被设置或已被取消。
protected void setException(Throwable t)
导致这个未来报告一个ExecutionException与给定的可抛弃的原因,除非这个未来已经被设置或被取消。
第一个方法表示获取call()方法的放回值存贮在FutureTask的值,下面是实现代码。
public class CallableDemo {
public static void main(String[] args) throws InterruptedException, ExecutionException {
// 创建线程池对象
ExecutorService pool = Executors.newFixedThreadPool(2);
// 可以执行Runnable对象或者Callable对象代表的线程
Future<Integer> f1 = pool.submit(new MyCallable(100));
Future<Integer> f2 = pool.submit(new MyCallable(200));
// V get()
Integer i1 = f1.get();
Integer i2 = f2.get();
System.out.println(i1);
System.out.println(i2);
// 结束
pool.shutdown();
}
}
public class MyCallable implements Callable<Integer> {
private int number;
public MyCallable(int number) {
this.number = number;
}
@Override
public Integer call() throws Exception {
int sum = 0;
for (int x = 1; x <= number; x++) {
sum += x;
}
return sum;
}
}
三:与线程相关的注意事项
1,run和start的区别:
run():仅仅是封装被线程执行的代码,直接调用是普通方法
start():首先启动了线程,然后再由jvm去调用该线程的run()方法。
2,为何需要重写run方法
不是类中的所有代码都需要被线程执行的。而这个时候,为了区分哪些代码能够被线程执行,java提供了Thread 类中的run()用来包含那些被线程执行的代码。
3,多次启动线程会怎么样?
出现异常:在第二次调用start()方法的时候,线程可能处于终止或者其它(非NEW)状态,但是不论如何,都是不可以再次启动的。
四:与线程相关的方法
1,线程名的获取和设置
设置名称: public final String setName()或者构造方法来设置线程的名称。
获取名称: public final String getName():获取线程的名称。public static Thread currentThread():返回当前正在执行的线程对象
2,线程优先级:线程优先级默认为5,范围为1至10;
通过: public final int getPriority():获取优先级
通过:public final void setPriority(int newPriority):设置优先级
五:线程控制
1,线程休眠: public static void sleep(long millis)
public class ThreadSleepDemo {
public static void main(String[] args) {
ThreadSleep ts1 = new ThreadSleep();
ThreadSleep ts2 = new ThreadSleep();
ThreadSleep ts3 = new ThreadSleep();
ts1.setName("林青霞");
ts2.setName("林志玲");
ts3.setName("林志颖");
ts1.start();
ts2.start();
ts3.start();
}
}
public class ThreadSleep extends Thread {
@Override
public void run() {
for (int x = 0; x < 100; x++) {
System.out.println(getName() + ":" + x + ",日期:" + new Date());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
2,加入线程:public final void join():等待该线程终止。
public class ThreadJoinDemo {
public static void main(String[] args) {
ThreadJoin tj1 = new ThreadJoin();
ThreadJoin tj2 = new ThreadJoin();
ThreadJoin tj3 = new ThreadJoin();
tj1.setName("李渊");
tj2.setName("李世民");
tj3.setName("李元霸");
tj1.start();
try {
tj1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
tj2.start();
tj3.start();
}}
public class ThreadJoin extends Thread {
@Override
public void run() {
for (int x = 0; x < 100; x++) {
System.out.println(getName() + ":" + x);
}}}
3,礼让线程:public static void yield():暂停当前正在执行的线程对象,并执行其他线程。 让多个线程的执行更和谐,但是不能靠它保证一人一次。
public class ThreadYieldDemo {
public static void main(String[] args) {
ThreadYield ty1 = new ThreadYield();
ThreadYield ty2 = new ThreadYield();
ty1.setName("林青霞");
ty2.setName("刘意");
ty1.start();
ty2.start();
}}
public class ThreadYield extends Thread {
@Override
public void run() {
for (int x = 0; x < 100; x++) {
System.out.println(getName() + ":" + x);
Thread.yield();
}}}
4, 守护线程: public final void setDaemon(boolean on):将该线程标记为守护线程或用户线程。当正在运行的线程都是守护线程时,Java 虚拟机退出。 该方法必须在启动线程前调用。
public class ThreadDaemonDemo {
public static void main(String[] args) {
ThreadDaemon td1 = new ThreadDaemon();
ThreadDaemon td2 = new ThreadDaemon();
td1.setName("关羽");
td2.setName("张飞");
// 设置收获线程
td1.setDaemon(true);
td2.setDaemon(true);
td1.start();
td2.start();
Thread.currentThread().setName("刘备");
for (int x = 0; x < 5; x++) {
System.out.println(Thread.currentThread().getName() + ":" + x);
}}}
public class ThreadDaemon extends Thread {
@Override
public void run() {
for (int x = 0; x < 100; x++) {
System.out.println(getName() + ":" + x);
}}}
5,线程中断:
public final void stop():让线程停止,过时了,但是还可以使用。
public void interrupt():中断线程。 把线程的状态终止,并抛出一个InterruptedException。
public class ThreadStopDemo {
public static void main(String[] args) {
ThreadStop ts = new ThreadStop();
ts.start();
try {
Thread.sleep(3000);
// ts.stop();
ts.interrupt();
} catch (InterruptedException e) {
e.printStackTrace();
}
}}
public class ThreadStop extends Thread {
@Override
public void run() {
System.out.println("开始执行:" + new Date());
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
// e.printStackTrace();
System.out.println("线程被终止了");
}
System.out.println("结束执行:" + new Date());
}
}
六:线程组
线程组是用于对功能相同的进行管理,可以对线程组内的全部线程进行统一管理。实现方式如下所示,只要是通过线程的构造方法实现的
查看API
java.lang.ThreadGroup
构造方法
ThreadGroup(String name) 构造一个新的线程组。
ThreadGroup(ThreadGroup parent, String name) 创建一个新的线程组。
主要方法:
void destroy() 销毁此线程组及其所有子组。
int getMaxPriority() 返回此线程组的最大优先级。
String getName() 返回此线程组的名称。
void setMaxPriority(int pri) 设置组的最大优先级。
代码代码如下:
public class ThreadGroupDemo {
public static void main(String[] args) {
method2();
}
private static void method2() {
// ThreadGroup(String name)
ThreadGroup tg = new ThreadGroup("这是一个新的组");
MyRunnable my = new MyRunnable();
// Thread(ThreadGroup group, Runnable target, String name)
Thread t1 = new Thread(tg, my, "林青霞");
Thread t2 = new Thread(tg, my, "刘意");
System.out.println(t1.getThreadGroup().getName());
System.out.println(t2.getThreadGroup().getName());
//通过组名称设置后台线程,表示该组的线程都是后台线程
tg.setDaemon(true);
}
}
七:线程池
1,线程池概述
A,为什么使用线程池:因为每次启动一个新的线程,系统都会给它开辟一个新的资源空间,会耗费资源,特别是在需要开辟大量的线程的时候,更是如此,所以使用线程池。
B:线程池:系统会开辟一个空间,专门用于存在被执行之后的线程,等到下次需要调用的时候,无需重新创建线程就可以从线程池内建对象拿出来使用。
2,实现
JDK5新增了一个Executors工厂类来产生线程池,有如下几个方法
public static ExecutorService newCachedThreadPool() :创建具有缓存功能的线程
public static ExecutorService newFixedThreadPool(int nThreads):创建存贮多个线程的线程池
public static ExecutorService newSingleThreadExecutor():创建存贮单个线程的线程池
这些方法的返回值是ExecutorService对象,该对象表示一个线程池,可以执行Runnable对象或者Callable对象代表的线程。它提供了如下方法
Future<?> submit(Runnable task) <T>
Future<T> submit(Callable<T> task)
实现代码:
public class ExecutorsDemo {
public static void main(String[] args) {
ExecutorService pool = Executors.newFixedThreadPool(2);
// 可以执行Runnable对象或者Callable对象代表的线程
pool.submit(new MyRunnable());
pool.submit(new MyRunnable());
//结束线程池
pool.shutdown();
}
}
public class MyRunnable implements Runnable {
@Override
public void run() {
for (int x = 0; x < 100; x++) {
System.out.println(Thread.currentThread().getName() + ":" + x);
}
}
}
3,实现原理(来源:疯狂哈丘 )

就是一个线程集合workerSet和一个阻塞队列workQueue。当用户向线程池提交一个任务(也就是线程)时,线程池会先将任务放入workQueue中。workerSet中的线程会不断的从workQueue中获取线程然后执行。当workQueue中没有任务的时候,worker就会阻塞,直到队列中有任务了就取出来继续执行。
八,线程安全
1,为什么会导致线程安全问题
多线程环境,共享数据,对共享数据进行操作。
2,解决线程安全问题
A: 同步代码块
synchronized(对象){需要同步的代码;}
注意:可以是任意锁,但是必须使用的是同一个对象。同步方法锁对象为;this;静态同步方法锁对象:类的字节码文件对象。
B:Lock锁(接口):Lock void lock():加锁; void unlock() :释放锁;ReentrantLock(子实现类);
如果被加锁的代码块出现异常,就不会在执行 unlock(),就无法释放锁,这时候需要将释放锁的操作早finally中,保证出问题还能将锁释放。
public class SellTicketDemo {
public static void main(String[] args) {
// 创建资源对象
SellTicket st = new SellTicket();
// 创建三个窗口
Thread t1 = new Thread(st, "窗口1");
Thread t2 = new Thread(st, "窗口2");
Thread t3 = new Thread(st, "窗口3");
// 启动线程
t1.start();
t2.start();
t3.start();
}}
public class SellTicket implements Runnable {
// 定义票
private int tickets = 100;
// 定义锁对象
private Lock lock = new ReentrantLock();
public void run() {
while (true) {
try {
// 加锁
lock.lock();
if (tickets > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()
+ "正在出售第" + (tickets--) + "张票");
}
} finally {
// 释放锁
lock.unlock();
}}}}
3,死锁问题: 是指两个或者两个以上的线程在执行的过程中,因争夺资源产生的一种互相等待现象,例如同步代码块的嵌套
public class DieLockDemo {
public static void main(String[] args) {
DieLock dl1 = new DieLock(true);
DieLock dl2 = new DieLock(false);
dl1.start();
dl2.start();
}
}
public class DieLock extends Thread {
private boolean flag;
public DieLock(boolean flag) {
this.flag = flag;
}
@Override
public void run() {
if (flag) {
synchronized (MyLock.objA) {
System.out.println("if objA");
synchronized (MyLock.objB) {
System.out.println("if objB");
}
}
} else {
synchronized (MyLock.objB) {
System.out.println("else objB");
synchronized (MyLock.objA) {
System.out.println("else objA");
}
}
}
}
}
public class MyLock {
// 创建两把锁对象
public static final Object objA = new Object();
public static final Object objB = new Object();
}
这时候,会出现相互等待的问题。
解决方案:生产消费者模式之等待唤醒机制
生产方有资源,就通知并等待消费者来消费;消费者需要消费,就通知生产者生产。
共享的资源:
共同资源
public class Student {
private String name;
private int age;
private boolean flag; // 默认情况是没有数据,如果是true,说明有数据
public synchronized void set(String name, int age) {
// 如果有数据,就等待
if (this.flag) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 设置数据
this.name = name;
this.age = age;
// 修改标记
this.flag = true;
this.notify();
}
public synchronized void get() {
// 如果没有数据,就等待
if (!this.flag) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 获取数据
System.out.println(this.name + "---" + this.age);
// 修改标记
this.flag = false;
this.notify();
}
}
生产者:
public class SetThread implements Runnable {
private Student s;
private int x = 0;
public SetThread(Student s) {
this.s = s;
}
public void run() {
while (true) {
if (x % 2 == 0) {
s.set("林青霞", 27);
} else {
s.set("刘意", 30);
}
x++;
}}}
消费者:
public class GetThread implements Runnable {
private Student s;
public GetThread(Student s) {
this.s = s;
}
@Override
public void run() {
while (true) {
s.get();
}
}
测试:
public class StudentDemo {
public static void main(String[] args) {
//创建资源
Student s = new Student();
//设置和获取的类
SetThread st = new SetThread(s);
GetThread gt = new GetThread(s);
//线程类
Thread t1 = new Thread(st);
Thread t2 = new Thread(gt);
//启动线程
t1.start();
t2.start();
}
}

浙公网安备 33010602011771号