概述
------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------
Java 多线程
1.多线程概述
多线程概述
进程:
正在运行的程序,是系统进行资源分配和调用的独立单位。
每一个进程都有它自己的内存空间和系统资源。
线程:
是进程中的单个顺序控制流,是一条执行路径
一个进程如果只有一条执行路径,则称为单线程程序。
一个进程如果有多条执行路径,则称为多线程程序。
举例
扫雷游戏,迅雷下载等
多线程有什么意义?
多线程的作用不是提高了执行速度,而是为了提高应用程序的使用率.而多线程却给了我们一个错觉:让我们觉得多个线程是并发执行的,其实不是.因为多个线程共享同一个进程的资源(堆内存和方法区),但是栈内存是独立的,一个线程一个栈.所以他们仍然是在抢CPU的资源执行,一个时间点上只有一个线程执行.而且谁抢到,这个不一定,所以造成了线程运行的随机性.
并发和并行?
区别:并发是物理上同时发生,指在某一个时间点同时运行多个程序.
并行是逻辑上同时发生,指在某一个时间内同时运行多个程序.
Java程序运行原理
java 命令会启动 java 虚拟机,启动 JVM,等于启动了一个应用程序,也就是启动了一个进程。该进程会自动启动一个 “主线程” ,然后主线程去调用某个类的 main 方法。所以 main方法运行在主线程中。在此之前的所有程序都是单线程的。
思考:jvm虚拟机的启动是单线程的还是多线程的? 多线程(至少启动了垃圾回收线程和主线程).
2.多线程实现方案
两种方案:继承Thread类和实现Runnable接口
1继承Thread类, 重写 Thread 类的 run 方法,分配并启动该子类的实例.
public class MyThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.err.println(i);
}
}
}
public static void main(String[] args) {
MyThread my = new MyThread();
my.start();
}
2实现Runnable接口. 声明实现 Runnable 接口的类。该类然后实现 run 方法。然后可以分配该类的实例,在创建 Thread 时作为一个参数来传递并启动。
public class MyRunnableClass implements Runnable {
public void run() {
// 由于现实接口的方式不能直接使用Thread类了,但是可以间接使用.
for (int i = 0; i < 100; i++) {
System.err.println(Thread.currentThread().getName()+":"+i);
}
}
}
public static void main(String[] args) {
MyRunnableClass target = new MyRunnableClass();
Thread t = new Thread(target);
Thread t2 = new Thread(target);
t.start();
t2.start();
}
在这个两种方案中都重写了run()方法,为什么呢? 因为不是类中所有的代码都需要被现实执行的.而这个时候,为了区分哪些代码能够被线程执行,java提供了Thread类中的run方法来包含那些被线程执行的代码.
public class MyThread extends Thread{
@Override
public void run() {
// 自己写代码
// System.out.println("好好学习,天天向上");
// 一般来说,被线程执行的代码肯定是比较耗时的。所以我们用循环改进
for (int x = 0; x < 200; x++) {
System.out.println(x);
}
}
}
Thread 类剖析
Thread类也实现了Runnable接口,因此实现了接口中run方法.
当生产一个线程对象时,如果没有为其指定线程名称,那么线程对象的名字将使用如下形式:Thread-number,该number是自动增加的数字,并被所有的Thread对象所共享,因为它是一个static的成员变量.下面是从Thread类抽取的部分代码:
public class Thread implements Runnable {
private char name[];
private Runnable target;
private static int threadInitNumber;
private static synchronized int nextThreadNum() {
return threadInitNumber++;
}
public Thread() {
init(null, null, "Thread-" + nextThreadNum(), 0);
}
private void init(ThreadGroup g, Runnable target, String name,
long stackSize) {
// ...代码
this.name = name.toCharArray();
// ...代码
}
}
当使用第一种方案(继承Thread的方式)来生成线程对象时,我们需要重写run()方法,因为Thread类的run()方法此时什么事情也不做。
当使用第二种方案(实现Runnable接口的方式)来生成线程对象时,我们需要实现Runnable接口的run()方法,然后使用new Thread(new MyRunnableClass())来生成线程对象(MyRunnableClass已经实现了Runnable接口),这时的线程对象的run()方法会调用MyRunnableClass的run()方法。该方案的好处:
1.可以避免由于Java单继承带来的局限性。
2.适合多个相同程序的代码去处理同一个资源的情况,把线程同程序的代码,数据有效分离,较好的体现了面向对象的设计思想。
public class MyThread extends Thread{
int number = 10;
@Override
public void run() {
// 自己写代码
}
}
public class MyThreadDemo {
public static void main(String[] args) {
//两个对象,两份成员变量
MyThread my = new MyThread();
MyThread my2 = new MyThread();
my.start();
my2.start();
}
}
public class MyRunnableClass implements Runnable {
int number = 10;
public void run() {
// 由于现实接口的方式不能直接使用Thread类了,但是可以间接使用.
for (int i = 0; i < 100; i++) {
System.err.println(Thread.currentThread().getName()+":"+i);
}
}
}
public class MyRunnableDemo {
public static void main(String[] args) {
//只有一份成员变量
MyRunnableClass target = new MyRunnableClass();
Thread t = new Thread(target,"张三");
Thread t2 = new Thread(target,"李四");
t.start();
t2.start();
}
}
run()和start()的区别?
run():仅仅是封装被线程执行的代码,直接调用是普通方法,
start():首先启动了线程,然后再由jvm去调用该线程的run()方法.
3.线程调度和线程控制
线程有两种调度模型:
1分时调度模型
所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间片;
2抢占式调度模型
优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个了,优先级高的线程获取的 CPU 时间片相对多一些.
Java使用的是抢占式调度模型.那么如何设置和获取线程优先级?
public final void setPriority(int newPriority) 更改线程的优先级
public final int getPriority() 返回线程的优先级,线程默认优先级 5.
public class ThreadPriority extends Thread {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.err.println(getName()+":"+i);
}
}
}
public static void main(String[] args) {
ThreadPriority tp1 = new ThreadPriority();
ThreadPriority tp2 = new ThreadPriority();
ThreadPriority tp3 = new ThreadPriority();
//线程优先级 min=1 nom = 5 max = 10
int priority = tp1.getPriority();
System.err.println(priority);
tp1.setPriority(10);//最高优先级
tp3.setPriority(1);//最低优先级
tp1.start();
tp2.start();
tp3.start();
}
线程控制
上面已经知道了线程的调度,接下来我们就可以使用如下方法对象线程进行控制.
线程休眠(暂停执行)
public static void sleep(long millis)
try {
Thread.sleep(100);
System.err.println(getName()+":"+new Date());
} catch (InterruptedException e) {
e.printStackTrace();
}
线程加入, 等待该线程终止
public final void join()
public class ThreadJion extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.err.println(getName()+":"+new Date());
}
}
}
public static void main(String[] args) {
ThreadJion tj1 = new ThreadJion();
ThreadJion tj2 = new ThreadJion();
ThreadJion tj3 = new ThreadJion();
tj1.setName("李渊");
tj2.setName("李世民");
tj3.setName("李元霸");
tj1.start();
try {
tj1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
// tj1执行完了之后,tj2,tj3才开始抢cpu资源
tj2.start();
tj3.start();
}
线程礼让, 暂停当前正在执行的线程对象,并执行其他线程
public static void yield()
public class ThreadYield extends Thread {
@Override
public void run() {
for (int i = 0; i < 30; i++) {
System.err.println(getName()+":"+i);
}
}
}
public static void main(String[] args) {
ThreadYield ty1 = new ThreadYield();
ThreadYield ty2 = new ThreadYield();
ty1.setName("张三");
ty2.setName("张三老婆");
ty1.start();
Thread.yield();
ty2.start();
}
public final void setDaemon(boolean on): 将该线程标记为守护线程或用户线程。当正在运行的线程都是守护线程时,Java 虚拟机退出.该方法必须在启动线程前调用。
public class ThreadDaemon extends Thread {
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.err.println(getName()+":"+i);
}
}
}
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();
// 设置main主线程的名称
Thread.currentThread().setName("刘备");
for (int i = 0; i < 5; i++) {
System.err.println(Thread.currentThread().getName() + ":" + i);
}
}
当线程”刘备”执行完成之后,线程”关羽”,”张飞”也随之结束.
中断线程
public final void stop()[已过时]
该方法具有固有的不安全性。用Thread.stop 来终止线程将释放它已经锁定的所有监视器.
public void interrupt():中断线程,如果当前线程没有中断它自己.
如果线程在调用Object 类的wait()、wait(long)或wait(long, int) 方法,或者该类的 join()、join(long)、join(long, int)、sleep(long) 或 sleep(long, int)方法过程中受阻,则其中断状态将被清除,它还将收到一个InterruptedException.
public class ThreadStop extends Thread {
@Override
public void run() {
// TODO Auto-generated method stub
System.err.println("开始执行>>"+new Date());
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
System.err.println("线程终止了...");
}
System.err.println("结束执行<<"+new Date());
}
}
stop方法终止线程:
public class ThreadStopDemo {
@SuppressWarnings("deprecation")
public static void main(String[] args) {
ThreadStop ts = new ThreadStop();
ts.start();
// 超过3秒,就结束线程.
try {
Thread.sleep(3000);
ts.stop();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
interrupt方法终止线程:
public class ThreadStopDemo2 {
public static void main(String[] args) {
ThreadStop ts = new ThreadStop();
ts.start();
// 超过3秒,就结束线程.
try {
Thread.sleep(3000);
ts.interrupt();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
程序运行输出:
开始执行>>Tue Aug 25 17:35:12
线程终止了...
结束执行<<Tue Aug 25 17:35:15
4.线程生命周期
线程的生命周期可以分为四个状态:
1.创建状态:
当用new操作符创建一个新的线程对象时,该线程处于创建状态。
处于创建状态的线程只是一个空的线程对象,系统不为它分配资源。
2.可运行状态:
执行线程的start()方法将为线程分配必须的系统资源,安排其运行,并调用线程体——run()方法,这样就使得该线程处于可运行状态(Runnable)。
这一状态并不是运行中状态(Running),因为线程也许实际上并未真正运行。
3.不可运行状态:
当发生下列事件时,处于运行状态的线程会转入到不可运行状态:
调用了sleep()方法;
线程调用wait()方法等待特定条件的满足;
线程IO输入/输出阻塞。
返回可运行状态;
处于睡眠状态的线程在指定的时间过去后;
如果线程在等待某一条件,另一个对象必须通过notify()或notifyAll()方法通知等待线程条件的改变;
如果线程是因为输入输出阻塞,等待输入输出完成。
4.消亡状态:
当线程的run()方法执行结束后,该线程自然消亡。
5.线程同步
线程同步:线程同步是解决线程安全问题的实现.
线程同步的前提
1多个线程
2多个线程使用的是同一个锁对象
线程同步的好处
同步的出现解决了多线程的安全问题。
线程同步的弊端
当线程相当多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率。
同步代码块
格式:
synchronized(对象){需要同步的代码;}
同步可以解决安全问题的根本原因就在那个对象上。该对象如同锁的功能。
public class SaleTicket implements Runnable {
private int tickets = 100; // 是多线程环境,有共享数据,有多条语句操作
Object obj = new Object(); // 锁对象
public void run() {
while (true) {
synchronized(obj){ //同步代码块,obj是所对象
if (tickets > 0) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.err.println(Thread.currentThread().getName() + " 正在卖 " + (tickets--) + " 张票.");
}
}
}
}
}
同步方法与同步方法的锁对象
就是把同步关键字加到方法上,锁对象就是this
public class SaleTicket implements Runnable{
private int tickets = 100;
private int x = 0;
//同步代码块
public void run() {
while(tickets>0){
if(x%2==0){
synchronized(this){ // 锁对象
if(tickets>0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.err.println(Thread.currentThread().getName()+"正在售"+(tickets--)+"张票..");
}
}
}else{
sellTicket(); // 同步方法
}
x++;
}
}
//同步方法的锁对象this
private synchronized void sellTicket() {
if(tickets>0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.err.println(Thread.currentThread().getName()+"正在售"+(tickets--)+"张票..");
}
}
}
静态同步方法:
public class SaleTicket implements Runnable{
private static int tickets = 100;
private int x = 0;
//同步代码块
public void run() {
while(tickets>0){
if(x%2==0){
synchronized(SaleTicket.class){ // Class对象或类对象
if(tickets>0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.err.println(Thread.currentThread().getName()+"正在售第"+(tickets--)+"张票..");
}
}
}else{
sellTicket(); // 静态同步方法
}
x++;
}
}
//static同步方法
private static synchronized void sellTicket() {
if(tickets>0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.err.println(Thread.currentThread().getName()+"正在售第"+(tickets--)+"张票..");
}
}
}
虽然我们可以理解同步代码块和同步方法的锁对象问题,但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁,为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock.
public interface Lock 实现提供了比使用synchronized 方法和语句可获得的更广泛的锁定操作。
void lock() 获取锁
void unlock()释放锁
public class ReentrantLock extends Object implements Lock, Serializable
一个可重入的互斥锁Lock,它具有与使用synchronized 方法和语句所访问的隐式监视器锁相同的一些基本行为和语义,但功能更强大。public class SellTicket implements Runnable {
private int tickets = 100;
private Lock lock = new ReentrantLock();
@Override
public void run() {
// TODO Auto-generated method stub
while(true){
try {
lock.lock();
if(tickets > 0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.err.println(Thread.currentThread().getName()+"正在售第"+(tickets--)+"张票.");
}
}finally{
lock.unlock(); // 确保最终执行.
}
}
}
}
6.死锁
死锁:是指两个或者两个以上的线程在执行的过程中,因争夺资源产生的一种互相等待现象.
同步代码块的嵌套案例:
public class MyLock {
//两把锁对象
public static final Object lockA = new Object();
public static final Object lockB = new Object();
}
两把锁对象
/*
* 死锁
*/
public class DieLock extends Thread {
private boolean flag;
public DieLock(boolean flag) {
this.flag = flag;
}
// 同步嵌套,相互等待
@Override
public void run() {
if (flag) {
synchronized (MyLock.lockA) {
System.err.println("IF objA");
synchronized (MyLock.lockB) {
System.err.println("IF objB");
}
}
} else {
synchronized (MyLock.lockB) {
System.err.println("else objB");
synchronized (MyLock.lockA) {
System.err.println("else objA");
}
}
}
}
}
测试结果:
/*
* 同步弊端:
* A:效率低,
* B:容易产生死锁
* 死锁:
* 是指两个或者两个以上的线程在执行的过程中,因争夺资源产生的一种互相等待现象.
* 举例
* 生产者和消费者
*/
public class DieLockDemo {
public static void main(String[] args) {
DieLock dl1 = new DieLock(true);
DieLock dl2 = new DieLock(false);
dl1.start();
dl2.start();
}
}
运行结果:出现互相等待的结果。
else objB
IF objA
7.线程间通信
线程间通信:不同种类的线程针对同一个资源的操作.
举例: 通过设置线程(生产者)和获取线程(消费者)针对同一个学生对象进行操作./*
* 通过设置线程(生产者)和获取线程(消费者)针对同一个学生对象进行操作.
* 资源类:Student
* 设置学生数据:SetThread(生产者)
* 获取学生数据:GetThread(消费者)
* 测试类:StudentDemo
*/
public class StudentDemo {
public static void main(String[] args) {
//创建资源
Student s = new Student(); // 确保是同一个Student对象
//设置和获取的类
SetThread st = new SetThread(s);
GetThread gt = new GetThread(s);
// 线程类
Thread t1 = new Thread(st);
Thread t2 = new Thread(gt);
//启动线程
t2.start();
t1.start(); //null---27
}
}
public class Student {
String name ;
int age;
}
public class SetThread implements Runnable {
private Student s;
public SetThread(Student s) {
this.s = s;
}
@Override
public void run() {
while(true){//给出不同的值
if(x % 2 == 0){
s.name = "林青霞";
s.age = 27;
}else{
s.name = "林正英";
s.age = 55;
}
x++;
}
}
}
public class GetThread implements Runnable {
private Student s;
public GetThread(Student s) {
this.s = s;
}
@Override
public void run() {
// Student s = new Student();//另一个对象,不是同一个对象
while(true){
System.err.println(s.name + "---" + s.age);
}
}
}
程序运行结果:问题: 同一个数据出现多次,姓名和年龄不匹配。
林青霞---55
林正英---55
林正英---27
林正英---55
林青霞---55
林正英---55
林青霞---27
林青霞---55
当出现线程安全问题时,必须满足三个条件:
A: 是否是多线程 是
B:是否存在共享数据 是
C:是否有多条语句操作共享数据 是
解决方式:1. 通过等待唤醒机制实现数据依次出现.
2.把同步代码块改进为同步方法实现.
设置数据类:
public class SetThread implements Runnable {
private Student s;
public SetThread(Student s) {
this.s = s;
}
private int x = 0;
@Override
public void run() {
while(true){
synchronized (s) { //必须是同一把锁
if(x % 2 == 0){
s.name = "林青霞";
s.age = 27;
}else{
s.name = "林正英";
s.age = 55;
}
x++;
}
}
}
}
获取数据类:
public class GetThread implements Runnable {
private Student s;
public GetThread(Student s) {
this.s = s;
}
@Override
public void run() {
while(true){
synchronized (s) { //必须是同一把锁
System.err.println(s.name + "---" + s.age);
}
}
}
}
运行结果: 这样的做法是数据太分散了,不是一个一个的一次出现.
林正英---55
林正英---55
林青霞---27
林青霞---27
林青霞---27
林青霞---27
林正英---55
林正英---55
林正英---55
这个线程安全是解决了,但是一样存在着问题:
1. 如果消费者先抢到CPU的执行权,就会去消费数据,但是现在的数据是默认值。没有意义。
2. 如果生产者先抢到CPU的执行权,就会产生数据,但是呢,它产生完数据后,还继续拥有执行权,它就继续产生数据。应该等待消费者把数据消费掉,然后再生产。
那就应该是:
A:生产者: 先看是否有数据,有就等待,没有就生产.
B:消费者 : 先看是否有数据,有就消费,没有就等待.
为了处理这类问题,java提供了一种机制:等待唤醒机制。
/*
* 等待唤醒机制:Object类提供
* wait(),notify() 单个线程
* notifyAll() 所有线程
* 为什么这些方法定义在Object类中呢?
****这些方法的调用必须通过锁对象调用. 刚刚使用的锁对象是任意对象.
*/
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();
}
}
public class Student {
String name ;
int age;
boolean flag ;
}
public class SetThread implements Runnable {
private Student s;
public SetThread(Student s) {
this.s = s;
}
private int x = 0;
@Override
public void run() {
while(true){
synchronized (s) { //必须是同一把锁
// 判断有没有
if(s.flag){ //true有就等待
try {
s.wait(); // 释放锁,t2就会抢到
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if(x % 2 == 0){
s.name = "林青霞";
s.age = 27;
}else{
s.name = "林正英";
s.age = 55;
}
x++;
// 修改flag
s.flag = true;
s.notify(); // 唤醒--> t1 or t2,并不表示立马可以执行,还得抢CPU执行权
}
}
}
}
public class GetThread implements Runnable {
private Student s;
public GetThread(Student s) {
this.s = s;
}
@Override
public void run() {
while(true){
synchronized (s) { //必须是同一把锁
if(!s.flag){ // 没有就等待,默认!s.flag=true.等待
try {
s.wait(); // 释放锁,会在此处醒来
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.err.println(s.name + "---" + s.age);
// 修改flag
s.flag = false;
s.notify(); // 唤醒 --> t1 or t2
}
}
}
}
程序输出结果:
林青霞---27
林正英---55
林青霞---27
林正英---55
林青霞---27
林正英---55
生产者和消费者之等待唤醒机制代码优化:
/*
* 主体
*/
public class Student {
private String name;
private int age;
private boolean flag;
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;
// 修改flag
this.flag = true;
// 唤醒
this.notify();
}
public synchronized void get() {
// 如果没有数据就等待
if (!this.flag) {
try {
this.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
// 获取数据
System.err.println(this.name + "---" + this.age);
// 修改标记
this.flag = false;
// 唤醒
this.notify();
}
}
SetThread生产线程类:
public class SetThread implements Runnable {
private Student s;
public SetThread(Student s) {
this.s = s;
}
private int x = 0;
@Override
public void run() {
while (true) {
if (x % 2 == 0) {
s.set("林青霞", 27);
} else {
s.set("林正英", 55);
}
x++;
}
}
}
GetThread消费线程类:
public class GetThread implements Runnable {
private Student s;
public GetThread(Student s) {
this.s = s;
}
@Override
public void run() {
while (true) {
s.get(); // 数据源自己来做
}
}
}
测试Demo
/*
* 优化后的代码:
* 私有化了成员变量,把设置和获取的操作封装并加上同步功能。
* 设置和获取的线程中只需要调用方法即可。
*/
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();
}
}
程序1中的锁对象是student,程序2中的锁对象是this.
8.线程组和线程池
1. 线程组
Java中使用ThreadGroup来表示线程组,它可以对一批线程进行分类管理,Java允许程序直接对线程组进行控制。默认情况下,所有的线程都属于主线程组。
public final ThreadGroup getThreadGroup()
我们也可以给线程设置分组
Thread(ThreadGroup group,Runnable target, String name)
实例:
1 .获得线程所在的线程组
private static void method1() {
// TODO Auto-generated method stub
MyRunnable my = new MyRunnable();
Thread t1 = new Thread(my,"林青霞");
Thread t2 = new Thread(my,"Louis");
//获得线程组
ThreadGroup tg1 = t1.getThreadGroup();
ThreadGroup tg2 = t2.getThreadGroup();
System.err.println(tg1.getName()); // 线程所属线程组名称 ,默认是 main
System.err.println(tg2.getName()); // 线程所属线程组名称,默认是 main
//当前线程的线程组也是main
System.err.println(Thread.currentThread().getThreadGroup().getName());
// 默认情况下,所有的都属于同一个线程组
t1.start();
t2.start();
}
2 .设置新的线程组和管理组中的线程
private static void method2() {
ThreadGroup tg = new ThreadGroup("线程组ABC");
MyRunnable my = new MyRunnable();
Thread t1 = new Thread(tg,my,"林青霞"); // 指定线程组
Thread t2 = new Thread(tg,my,"Louis"); // 指定线程组
ThreadGroup tg1 = t1.getThreadGroup();
ThreadGroup tg2 = t2.getThreadGroup();
System.err.println(tg1.getName()); // 线程所属线程组名称
System.err.println(tg2.getName()); // 线程所属线程组名称
// 设置后台线程
tg.setDaemon(true); // 改组中所有线程都是守护线程
// 中断所有线程
tg.interrupt();
//销毁所有线程
tg.destroy();
}
public class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.err.println(i);
}
}
}
public class ThreadGroupDemo {
public static void main(String[] args) {
// 获得线程组
method1();
// 修改线程组 ,新建线程组
method2();
}
}
2.线程池
程序启动一个新线程成本是比较高的,因为它涉及到要与操作系统进行交互。而使用线程池可以很好的提高性能,尤其是当程序中要创建大量生存期很短的线程时,更应该考虑使用线程池。
线程池里的每一个线程代码结束后,并不会死亡,而是再次回到线程池中成为空闲状态,等待下一个对象来使用。
在JDK5之前,我们必须手动实现自己的线程池,从JDK5开始,Java内置支持线程池.
JDK5新增了一个Executors工厂类来产生线程池,有如下几个方法:
public static ExecutorServicenewCachedThreadPool()
public static ExecutorServicenewFixedThreadPool(int nThreads)
public static ExecutorServicenewSingleThreadExecutor()
这些方法的返回值是ExecutorService对象,该对象表示一个线程池,可以执行Runnable对象或者Callable对象代表的线程。它提供了如下方法:
Future<?> submit(Runnable task)
<T> Future<T> submit(Callable<T>task)
示例:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* 线程池:
* A:创建一个线程池,控制要创建几个线程对象。
* public static ExecutorService newFixedThreadPool(int nThreads)
* B:线程池的线程
* 可以执行Runnable对象或者Callable对象代表的线程
* C:调用方法
* Future<?> submit(Runnable task)
* <T> Future<T> submit(Callable<T> task)
* D:结束线程池
* shutdown
*/
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();
}
}
线程池中的线程实现Runnable或Cellable:
public class MyRunnable implements Runnable {
@Override
public void run() {
// TODO Auto-generated method stub
for (int i = 0; i < 100; i++) {
System.err.println(Thread.currentThread().getName()+":"+i);
}
}
}
3.多线程实现第三种方式
实现Callable接口,
public interface Callable<V>
Callable接口包含一个不带任何参数的call方法。
Callable 接口类似于 Runnable,两者都是为那些其实例可能被另一个线程执行的类设计的。但是 Runnable 不会返回结果,并且无法抛出经过检查的异常。
import java.util.concurrent.Callable;
/*
* public interface Callable<V> 线程的第三种实现方式
*/
public class MyCallable implements Callable<Object> {
@Override
public Object call() throws Exception {
for (int i = 0; i < 100; i++) {
System.err.println(Thread.currentThread().getName()+" : "+ i);
}
return null;
}
}
CallableDemo类:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* 线程池:
* A:创建一个线程池,控制要创建几个线程对象。
* public static ExecutorService newFixedThreadPool(int nThreads)
* B:线程池的线程
* 可以执行Runnable对象或者Callable对象代表的线程
* C:调用方法
* Future<?> submit(Runnable task)
* <T> Future<T> submit(Callable<T> task)
* D:结束线程池
* shutdown
*/
public class CallableDemo {
public static void main(String[] args) {
//创建一个线程池对象,控制要创建几个线程对象
ExecutorService pool = Executors.newFixedThreadPool(2);
//可以执行Runnable对象或Callable对象代表的线程
pool.submit(new MyCallable()); // 依赖于线程池实现,不能单独实现有局限性。
pool.submit(new MyCallable());
//关闭线程池
pool.shutdown();
}
}
该方式依赖于线程池实现,不能单独实现有局限性。
4.匿名内部类方式使用多线程
/*
* 匿名内部类实现多线程
*/
public class ThreadDemo {
public static void main(String[] args) {
// 1.
new Thread() { // 一次性使用
public void run() {
for (int i = 0; i < 100; i++)
System.err.println(Thread.currentThread().getName() + "--"+ i);
};
}.start();
// 2.
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 100; i++)
System.err.println(Thread.currentThread().getName() + "--"+ i);
}
}) {
}.start();
// 3. (run()){run()}
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 100; i++)
System.err.println("hello --"+ i);
}
}) {
@Override
public void run() {
for (int i = 0; i < 100; i++)
System.err.println("world --"+ i); // 运行{run()方法}
}
}.start();
}
}
在开发中会经常使用到匿名内部类方式实现多线程。
9.定时器的使用
定时器是一个应用十分广泛的线程工具,可用于调度多个定时任务以后台线程的方式执行。在Java中,可以通过Timer和TimerTask类来实现定义调度的功能Timer:
publicTimer() 创建一个新计时器。相关的线程不作为守护程序运行。
publicvoid schedule(TimerTask task, long delay)
安排在指定延迟后执行指定的任务。
参数:
task - 所要安排的任务。
delay -执行任务前的延迟时间,单位是毫秒。
publicvoid schedule(TimerTask task,long delay,long period)
安排指定的任务从指定的延迟后开始进行重复的固定延迟执行。以近似固定的时间间隔(由指定的周期分隔)进行后续执行。
参数:
task - 所要安排的任务。
delay -执行任务前的延迟时间,单位是毫秒。
period- 执行各后续任务之间的时间间隔,单位是毫秒。
TimerTask
publicabstract class TimerTaskextends Object implements Runnable
由 Timer 安排为一次执行或重复执行的任务。
publicabstract void run()
此计时器任务要执行的操作。
指定者:接口 Runnable 中的 run。
publicboolean cancel()
取消此计时器任务。如果任务安排为一次执行且还未运行,或者尚未安排,则永远不会运行。
开发中使用的定时器框架:Quartz是一个完全由java编写的开源调度框架。
示例1:执行定时任务
/*
* 定时器 : Timer 和 TimerTask定时器任务
* Timer: 定时
* TimerTask:任务
*/
public class TimerDemo {
public static void main(String[] args) {
// 创建定时器对象
Timer t = new Timer();
// 1秒后执行任务
// t.schedule(new MyTask(), 1000);
t.schedule(new MyTask(t), 1000);
}
}
class MyTask extends TimerTask {
private Timer t;
public MyTask(Timer t) {
this.t = t;
}
@Override
public void run() {
System.err.println("崩,爆炸了..");
t.cancel(); // 任务执行完之后,关闭定时器
}
}
示例2:周期性执行定时任务
publicvoid schedule(TimerTask task,long delay,long period)
delay -执行任务前的延迟时间,单位是毫秒。
period - 执行各后续任务之间的时间间隔,单位是毫秒。import java.util.Timer;
import java.util.TimerTask;
/*
* 定时器 : Timer 和 TimerTask定时器任务
* Timer: 定时
* TimerTask:任务
* 连续任务
* public void schedule(TimerTask task,long delay,long period)
* delay:延迟
* period:周期
*/
public class TimerDemo {
public static void main(String[] args) {
// 创建定时器对象
Timer t = new Timer();
// 1秒后执行,3秒后继续执行相同的动作
t.schedule(new MyTask(t), 1000, 3000);
}
}
class MyTask extends TimerTask {
private Timer t;
public MyTask(Timer t) {
this.t = t;
}
@Override
public void run() {
System.err.println("崩,爆炸了..");
// t.cancel();
}
}
10.多线程状态图
1.线程的生命周期
2.多线程同步状态
常见的情况:
1. 新建——就绪——运行——死亡
2. 新建——就绪——运行——就绪——运行——死亡
3. 新建——就绪——运行——其他阻塞——就绪——运行——死亡
4. 新建——就绪——运行——同步阻塞——就绪——运行——死亡
5. 新建——就绪——运行——等待阻塞——同步阻塞——就绪——运行——死亡
总结:
(1)多线程:一个应用程序有多条执行路径
进程:正在执行的应用程序
线程:进程的执行单元,执行路径
单线程:一个应用程序只有一条执行路径
多线程:一个应用程序有多条执行路径
多进程的意义?
提高CPU的使用率
多线程的意义?
提高应用程序的使用率
(2)Java程序的运行原理及JVM的启动是多线程的吗?
A:Java命令去启动JVM,JVM会启动一个进程,该进程会启动一个主线程。
B:JVM的启动是多线程的,因为它最低有两个线程启动了,主线程和垃圾回收线程。
(3)多线程的实现方案
A:继承Thread类
B:实现Runnable接口
(4)线程的调度和优先级问题
A:线程的调度
a:分时调度
b:抢占式调度 (Java采用的是该调度方式)
B:获取和设置线程优先级
a:默认是5
b:范围是1-10
(5)线程的控制(常见方法)
A:休眠线程
B:加入线程
C:礼让线程
D:后台线程
E:终止线程
(6)线程的生命周期
A:新建
B:就绪
C:运行
D:阻塞
E:死亡
(7)电影院卖票程序的实现
A:继承Thread类
B:实现Runnable接口
(8)电影院卖票程序出问题
A:为了更符合真实的场景,加入了休眠100毫秒。
B:卖票问题
a:同票多次
b:负数票
(9)多线程安全问题的原因(也是我们以后判断一个程序是否有线程安全问题的依据)
A:是否有多线程环境
B:是否有共享数据
C:是否有多条语句操作共享数据
(10)同步解决线程安全问题
A:同步代码块
synchronized(对象){
需要被同步的代码;
}
这里的锁对象可以是任意对象。
B:同步方法
把同步加在方法上。
这里的锁对象是this
C:静态同步方法
把同步加在方法上。
这里的锁对象是当前类的字节码文件对象(反射再讲字节码文件对象)
(11)回顾以前的线程安全的类
A:StringBuffer
B:Vector
C:Hashtable
D:如何把一个线程不安全的集合类变成一个线程安全的集合类
用Collections工具类的方法即可。
(12)JDK5以后的针对线程的锁定操作和释放操作
Lock锁
(13)死锁问题的描述和代码体现
(14)生产者和消费者多线程体现(线程间通信问题)
以学生作为资源来实现的
资源类:Student
设置数据类:SetThread(生产者)
获取数据类:GetThread(消费者)
测试类:StudentDemo
代码:
A:最基本的版本,只有一个数据。
B:改进版本,给出了不同的数据,并加入了同步机制
C:等待唤醒机制改进该程序,让数据能够实现依次的出现
wait()
notify()
notifyAll() (多生产多消费)
D:等待唤醒机制的代码优化。把数据及操作都写在了资源类中
(15)线程组
(16)线程池
(17)多线程实现的第三种方案
(18)多线程的面试题
1.多线程有几种实现方案,分别是哪几种?
两种,继承Thread类和实现Runnable接口
扩展一种,实现Callable接口.这个方式得和线程池结合.
2.同步有几种方式,分别是什么?
两种.同步代码块和同步方法.
3.启动一个线程是run()还是start()?他们的区别?
start()
run():封装了被线程执行的代码,直接调用仅和普通方法的调用一样.
start():启动线程,并由JVM自动调用run()方法.
4.sleep()和wait()方法的区别?
sleep():必须指定时间;不释放锁.
wait():可以指定时间,也可以不指定时间;释放锁.
5.为什么wait(),notify(),notifyAll()等方法都是定义在Object类中?
因为这些方法的调用都是依赖于锁对象的,而同步代码块的锁对象是任意锁,而Object代表任意对象,所以,定义在Object类中才合理.
6.线程生命周期
新建——就绪——运行——死亡
新建——就绪——运行——就绪——运行——死亡
新建——就绪——运行——其他阻塞——就绪——运行——死亡
新建——就绪——运行——同步阻塞——就绪——运行——死亡
新建——就绪——运行——等待阻塞——同步阻塞——就绪——运行——死亡
------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------
最后
以上就是细心路人为你收集整理的黑马程序员——Java 多线程Java 多线程的全部内容,希望文章能够帮你解决黑马程序员——Java 多线程Java 多线程所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复