多线程
- 什么是线程
- 线程是程序执行的一条路径, 一个进程中可以包含多条线程
- 多线程并发执行可以提高程序的效率, 可以同时完成多项工作(cpu不断切换任务)当然多核cpu是不一样的
多线程并行和并发的区别
- 并行就是两个任务同时运行,就是甲任务进行的同时,乙任务也在进行。(需要多核CPU)
- 并发是指两个任务都请求运行,而处理器只能按受一个任务,就把这两个任务安排轮流进行,由于时间间隔较短,使人感觉两个任务都在运行。
比如:
JVM的启动是多线程的吗
JVM启动至少启动了垃圾回收线程和主线程,所以是多线程的。
多线程程序实现的两种方式
- 继承Thread
- 定义类继承Thread
- 重写run方法
- 把新线程要做的事写在run方法中
- 创建线程对象
- 开启新线程, 内部会自动执行run方法
public class Demo2_Thread {
public static void main(String[] args) {
MyThread mt = new MyThread();
mt.start();
for(int i = 0; i < 1000; i++) {
System.out.println("b");
}
}
}
class MyThread extends Thread {
public void run() {
for(int i = 0; i < 1000; i++) {
System.out.println("a");
}
}
}
- 实现Runnable(方法二)
- 定义类实现Runnable接口
- 实现run方法
- 把新线程要做的事写在run方法中
- 创建自定义的Runnable的子类对象
- 创建Thread对象, 传入Runnable
- 调用start()开启新线程, 内部会自动调用Runnable的run()方法
public class Demo3_Runnable {
public static void main(String[] args) {
MyRunnable mr = new MyRunnable();
Thread t = new Thread(mr);
t.start();
for(int i = 0; i < 1000; i++) {
System.out.println("b");
}
}
}
class MyRunnable implements Runnable {
@Override
public void run() {
for(int i = 0; i < 1000; i++) {
System.out.println("a");
}
}
}
Runnable 底层也是依赖 Thread的
两种方式的区别
-
继承Thread : 由于子类重写了Thread类的run(), 当调用start()时, 直接找子类的run()方法
-
实现Runnable : 构造函数中传入了Runnable的引用, 成员变量记住了它, start()调用run()方法时内部判断成员变量Runnable的引用是否为空, 不为空编译时看的是Runnable的run(),运行时执行的是子类的run()方法
-
继承Thread
- 好处是:可以直接使用Thread类中的方法,代码简单
- 弊端是:如果已经有了父类,就不能用这种方法
-
实现Runnable接口
- 好处是:即使自己定义的线程类有了父类也没关系,因为有了父类也可以实现接口,而且接口是可以多实现的
- 弊端是:不能直接使用Thread中的方法需要先获取到线程对象后,才能得到Thread的方法,代码复杂
匿名内部类实现线程的两种方式
继承Thread类
new Thread() {
public void run() {
for(int i = 0; i < 1000; i++) {
System.out.println("a");
}
}
}.start();
实现Runnable接口
new Thread(new Runnable(){
public void run() {
for(int i = 0; i < 1000; i++) {
System.out.println("b");
}
}
}).start();
获取名字和设置名字
- 获取名字
- 通过getName()方法获取线程对象的名字
- 设置名字
- 通过构造函数可以传入String类型的名字
new Thread("xxx") {
public void run() {
for(int i = 0; i < 1000; i++) {
System.out.println(this.getName() + "***");
}
}
}.start();
new Thread("yyy") {
public void run() {
for(int i = 0; i < 1000; i++) {
System.out.println(this.getName() + "++++");
}
}
}.start();
通过setName(String)方法可以设置线程对象的名字
Thread t1 = new Thread() {
public void run() {
for(int i = 0; i < 1000; i++) {
System.out.println(this.getName() + "*****");
}
}
};
Thread t2 = new Thread() {
public void run() {
for(int i = 0; i < 1000; i++) {
System.out.println(this.getName() + "+++++");
}
}
};
t1.setName("xixi");
t2.setName("哈哈");
t1.start();
t2.start();
获取当前线程的对象
Thread.currentThread(), 主线程也可以获取
new Thread(new Runnable() {
public void run() {
for(int i = 0; i < 1000; i++) {
System.out.println(Thread.currentThread().getName() + "*****");
}
}
}).start();
new Thread(new Runnable() {
public void run() {
for(int i = 0; i < 1000; i++) {
System.out.println(Thread.currentThread().getName() + "++++");
}
}
}).start();
Thread.currentThread().setName("我是主线程");
System.out.println(Thread.currentThread().getName());
休眠线程
- Thread.sleep(毫秒,纳秒), 控制当前线程休眠若干毫秒1秒= 1000毫秒 1秒 = 1000 * 1000 * 1000纳秒 1000000000
守护线程
- setDaemon(), 设置一个线程为守护线程, 该线程不会单独执行, 当其他非守护线程都执行结束后, 自动退出
Thread t1 = new Thread() {
public void run() {
for(int i = 0; i < 50; i++) {
System.out.println(getName() + "****");
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
Thread t2 = new Thread() {
public void run() {
for(int i = 0; i < 5; i++) {
System.out.println(getName() + "++++");
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
t1.setDaemon(true); //将t1设置为守护线程
t1.start();
t2.start();
加入线程
- join(), 当前线程暂停, 等待指定的线程执行结束后, 当前线程再继续
- join(int), 可以等待指定的毫秒之后继续
final Thread t1 = new Thread() {
public void run() {
for(int i = 0; i < 50; i++) {
System.out.println(getName() + "*****");
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
Thread t2 = new Thread() {
public void run() {
for(int i = 0; i < 50; i++) {
if(i == 2) {
try {
t1.join(30);
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(getName() + "+++++");
}
}
};
t1.start();
t2.start();
礼让线程和设置线程的优先级
- yield()让出cpu
- setPriority()设置线程的优先级
同步代码块
- 什么情况下需要同步
- 当多线程并发, 有多段代码同时执行时, 我们希望某一段代码执行的过程中CPU不要切换到其他线程工作. 这时就需要同步.
- 如果两段代码是同步的, 那么同一时间只能执行一段, 在一段代码没执行结束之前, 不会执行另外一段代码.
- 同步代码块
- 使用synchronized关键字加上一个锁对象来定义一段代码, 这就叫同步代码块
- 多个同步代码块如果使用相同的锁对象, 那么他们就是同步的
public static void main(String[] args) {
final Printer p = new Printer();
new Thread() {
public void run() {
while(true) {
p.print1();
}
}
}.start();
new Thread() {
public void run() {
while(true) {
p.print2();
}
}
}.start();
}
}
class Printer {
Demo d = new Demo();
public static void print1() {
synchronized(d){
//锁对象可以是任意对象,但是被锁的代码需要保证是同一把锁,不能用匿名对象
System.out.print("A");
System.out.print("B");
System.out.print("C");
System.out.print("D");
System.out.print("\r\n");
}
}
public static void print2() {
synchronized(d){
System.out.print("1");
System.out.print("2");
System.out.print("3");
System.out.print("4");
System.out.print("\r\n");
}
}
}
同步方法
- 使用synchronized关键字修饰一个方法, 该方法中所有的代码都是同步的
class Printer {
public static void print1() {
synchronized(Printer.class){
//锁对象可以是任意对象,但是被锁的代码需要保证是同一把锁,不能用匿名对象
System.out.print("A");
System.out.print("B");
System.out.print("C");
System.out.print("D");
System.out.print("\r\n");
}
}
/*
* 非静态同步函数的锁是:this
* 静态的同步函数的锁是:字节码对象
*/
public static synchronized void print2() {
System.out.print("1");
System.out.print("2");
System.out.print("3");
System.out.print("4");
System.out.print("\r\n");
}
}
单例设计模式
- 单例设计模式:保证类在内存中只有一个对象。
- 如何保证类在内存中只有一个对象呢?
- (1)控制类的创建,不让其他类来创建本类的对象。private
- (2)在本类中定义一个本类的对象。Singleton s;
- (3)提供公共的访问方式。 public static Singleton getInstance(){return s}
- 单例写法两种:
- (1)饿汉式 开发用这种方式。
class Singleton {
//1,私有构造函数
private Singleton(){}
//2,创建本类对象
private static Singleton s = new Singleton();
//3,对外提供公共的访问方法
public static Singleton getInstance() {
return s;
}
public static void print() {
System.out.println("11111111111");
}
}
- (2)懒汉式 面试写这种方式。多线程的问题?
//懒汉式,单例的延迟加载模式
class Singleton {
//1,私有构造函数
private Singleton(){}
//2,声明一个本类的引用
private static Singleton s;
//3,对外提供公共的访问方法
public static Singleton getInstance() {
if(s == null)
//线程1,线程2 线程不安全
s = new Singleton();
return s;
}
}
饿汉和懒汉的区别:
1.饿汉空间换时间,懒汉相反
2.懒汉线程不安全
- (3)第三种格式
class Singleton {
private Singleton() {}
public static final Singleton s = new Singleton();
//final是最终的意思,被final修饰的变量不可以被更改
}
Runtime类是一个单例类
Timer
它是java的定时器,可以定时执行一个任务,或者间隔一段时间重复执行一个任务
schedule()方法
public class DTimer {
public static void main(String[] args) throws InterruptedException {
Timer t = new Timer();
t.schedule(new MyTimerTask(), new Date(118,12,7,10,54,20),5000);
//第一个参数 是一个继承TimerTask类的类 对象 继承要重写run()方法 ,也就是要多线程执行内容
//第二个参数是Date对象
//第三个参数是间隔时间
}
}
class MyTimerTask extends TimerTask {
public void run() {
System.out.println("该敲代码了");
}
}
等待线程和唤醒线程
对象.wait()让一个线程等待
对象.notify()任意唤醒一个线程
对象.notify()唤醒所有等待线程
注意点:
- 1,在同步代码块中,用哪个对象锁,就用哪个对象调用wait方法
- 2,为什么wait方法和notify方法定义在Object这类中?
因为锁对象可以是任意对象,Object是所有的类的基类,所以wait方法和notify方法需要定义在Object这个类中 - 3,sleep方法和wait方法的区别?
a,sleep方法必须传入参数,参数就是时间,时间到了自动醒来
wait方法可以传入参数也可以不传入参数,传入参数就是在参数的时间结束后等待,不传入参数就是直接等待
b,sleep方法在同步函数或同步代码块中,不释放锁,睡着了也抱着锁睡
wait方法在同步函数或者同步代码块中,释放锁
多个线程的执行问题(互斥锁运用)
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
public class Demo3_ReentrantLock {
public static void main(String[] args) {
final Printer3 p = new Printer3();
new Thread() {
public void run() {
while(true) {
try {
p.print1();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}.start();
new Thread() {
public void run() {
while(true) {
try {
p.print2();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}.start();
new Thread() {
public void run() {
while(true) {
try {
p.print3();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}.start();
}
}
class Printer3 {
private ReentrantLock r = new ReentrantLock();
private Condition c1 = r.newCondition();
private Condition c2 = r.newCondition();
private Condition c3 = r.newCondition();
private int flag = 1;
public void print1() throws InterruptedException {
r.lock(); //获取锁
if(flag != 1) {
c1.await();
}
System.out.print("黑");
System.out.print("马");
System.out.print("程");
System.out.print("序");
System.out.print("员");
System.out.print("\r\n");
flag = 2;
//this.notify(); //随机唤醒单个等待的线程
c2.signal();
r.unlock(); //释放锁
}
public void print2() throws InterruptedException {
r.lock();
if(flag != 2) {
c2.await();
}
System.out.print("传");
System.out.print("智");
System.out.print("播");
System.out.print("客");
System.out.print("\r\n");
flag = 3;
//this.notify();
c3.signal();
r.unlock();
}
public void print3() throws InterruptedException {
r.lock();
if(flag != 3) {
c3.await();
}
System.out.print("i");
System.out.print("t");
System.out.print("h");
System.out.print("e");
System.out.print("i");
System.out.print("m");
System.out.print("a");
System.out.print("\r\n");
flag = 1;
c1.signal();
r.unlock();
}
}
java的多线程分组
Java中使用ThreadGroup来表示线程组,它可以对一批线程进行分类管理,Java允许程序直接对线程组进行控制
而默认情况下 都是main组
public final ThreadGroup getThreadGroup()//通过线程对象获取他所属于的组
public final String getName()//通过线程组对象获取他组的名字
如何设置线程组
我们也可以给线程设置分组
1,ThreadGroup(String name) 创建线程组对象并给其赋值名字
2,创建线程对象
3,Thread(ThreadGroup?group, Runnable?target, String?name)
4,设置整组的优先级或者守护线程
线程的五种状态
新建(初始),就绪(可运行),运行,阻塞,死亡(结束)
线程池 java jdk5开始
- 线程池概述
- 程序启动一个新线程成本是比较高的,因为它涉及到要与操作系统进行交互。而使用线程池可以很好的提高性能,尤其是当程序中要创建大量生存期很短的线程时,更应该考虑使用线程池。线程池里的每一个线程代码结束后,并不会死亡,而是再次回到线程池中成为空闲状态,等待下一个对象来使用。在JDK5之前,我们必须手动实现自己的线程池,从JDK5开始,Java内置支持线程池
- 内置线程池的使用概述
- JDK5新增了一个Executors工厂类来产生线程池,有如下几个方法
- public static ExecutorService newFixedThreadPool(int nThreads)
- public static ExecutorService newSingleThreadExecutor()
- 这些方法的返回值是ExecutorService对象,该对象表示一个线程池,可以执行Runnable对象或者Callable对象代表的线程。它提供了如下方法
- Future<?> submit(Runnable task)
- Future submit(Callable task)
- JDK5新增了一个Executors工厂类来产生线程池,有如下几个方法
* 使用步骤:
* 创建线程池对象
* 创建Runnable实例
* 提交Runnable实例
* 关闭线程池