1.多线程概述
-
进程:一个程序运行,程序在内存中分配的那片空间。
-
线程:进程中一个执行单元执行路径
进程中至少有一个线程,如果进程中有多个线程,就是多线程的程序。
-
并行与并发:
并行:某一时间点,有多个程序同时执行,多核CPU运行 并发:某一时间段,有多个程序同时执行,并不是真正意义的同时执行。 为多线程。
-
并发真的是同时执行吗?
不是,而是时间间隔很短,造成同时执行感觉。
-
多线程优势?
提高了用户体验,提高了程序的运行效率,提高CPU使用率。
2.开启线程两种方式
-
开启线程
/* * 1.继承Thread * 2.重写run方法 * 3.创建子类的对象 * 4.调用start方法 * */ public class ThreadDemo1 { public static void main(String[] args) { // 创建子类的对象 Demo d1 = new Demo("Jack"); Demo d2 = new Demo("Tom"); // 设置线程名字 d1.setName("d1"); // 获取d1执行线程名字 System.out.println(d1.getName()); // 获取当前线程名字 System.out.println(Thread.currentThread().getName()); // 调用start方法 d1.start(); d2.start(); } } // 继承Thread class Demo extends Thread{ String nickName; public Demo(String nickName) { this.nickName = nickName; } // 重写run方法 public void run() { for(int i=0;i<30;i++) { System.out.println(nickName + "---" + i); } } } // 整个运行过程有三个线程运行,主线程开启d1和d2线程
-
run方法与start方法区别
start:开启新的线程,会自动调用run方法在新的线程中执行 run:没有开启新的线程,只是普通方法
-
开启新线程第二种方式
声明实现Runnable接口的类,该类然后实现run方法,然后可以分配该类的实例,在创建Thread时做为一个参数来传递并启动,采用这种风格的同一个例子
/* * 实现多线程第二种方式: * 1.实现Runnable * 2.重写run方法 * 3.创建Runnable子类的对象 * 4.创建Thread类的对象,把第三步的对象传到构造方法中 * 5.使用Thread子类对象,调用start方法 * */ public class ThreadDemo2 { public static void main(String[] args) { Demo5 d = new Demo5();// 只有Thread或子类线程对象才是线程对象。它只是线程任务度夏宁。 Thread th = new Thread(d); // th才是线程对象 Thread th2 = new Thread(d); th.start(); th2.start(); } } class Demo5 implements Runnable{ public void run() { for (int i=0;i<20;i++) { System.out.println(Thread.currentThread().getName() + "---" + i); } } }
-
两种实现方式,区别是?
第一种方式有局限性的,因为Java是单继承,如果一个类已经有一个继承,它就不能再继承Thread类,就无法实现多线程。而第二种实现通过接口方式实现更合理,并且第二种方式更加符合面向对象特点:高内聚低耦合,把线程对象和线程任务分离开了。
3.线程中方法
1.sleep方法使用
public Static void sleep(long millis)
静态方法 进入阻塞状态,时间结束后进入可执行
- 示例:
Thread.sleep(3000);
sleep方法让谁阻塞,取决于他在哪个线程中。
2.join方法使用
public final void join()
被谁调用,让哪个线程先执行,执行完毕后,再执行所在线程
- 示例
public class JoinDemo1 {
public static void main(String[] args) {
Sum s = new Sum();
s.start();
// join:用谁调用,就让那个线程先执行,执行完毕后,再执行他所在线程(将他所在线程阻塞)。
try {
s.join();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Sum.sum);
}
}
class Sum extends Thread{
static int sum = 0;
public void run() {
for(int i=0;i<=1000;i++) {
sum += i;
}
}
}
3.yield方法
public static void yield()
让其他线程先执行,不一定生效,因为让谁执行是CPU决定的
4.stop方法
停止一个线程
5.interrupt 方法
打断线程的阻塞状态,进入可执行状态,会抛出异常
- 示例:打断子线程阻塞
public class interruptDemo {
public static void main(String[] args) {
Demo3 d = new Demo3();
d.start();
// 将d执行线程阻塞状态打断。
d.interrupt();
System.out.println("over");
}
}
class Demo3 extends Thread{
public void run() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
for (int i=0;i<10;i++) {
System.out.println(i);
}
}
}
- 打断主线程阻塞
public class InterruptDemo2 {
public static void main(String[] args) {
// 主线程传给Demo4
Demo4 d = new Demo4(Thread.currentThread());
d.start();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("over");
}
}
class Demo4 extends Thread{
Thread th;
public Demo4(Thread th) {
this.th = th;
}
// 用于打断主线程
public void run() {
th.interrupt();
}
}
- 练习:创建两个线程,一个线程负责打印大写字母表,一个线程负责打印小写字母表
public class Threadlianxi1 {
public static void main(String[] args) {
Thread th1 = new Thread(new Task1());
Thread th2 = new Thread(new Task2());
th1.start();
th2.start();
}
}
class Task1 implements Runnable{
public void run() {
for(char i='a';i<='z';i++) {
System.out.println(i);
}
}
}
class Task2 implements Runnable{
public void run() {
for(char i='A';i<='Z';i++) {
System.out.println(i);
}
}
}
4.线程生命的周期
主线程执行时候在栈空间,开辟空间给子线程,而他们的之间栈是独立的,但他们堆空间数据是共享的
5.线程安全的问题
- 买票示例:
public class SellTicketsDemo {
public static void main(String[] args) {
Tickets t = new Tickets();
Thread th1 = new Thread(t);
Thread th2 = new Thread(t);
Thread th3 = new Thread(t);
th1.start();
th2.start();
th3.start();
}
}
class Tickets implements Runnable{
static int tickets = 100;
public void run() {
while (true) {
if (tickets> 0) {
try {
Thread.sleep(30);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "====" + "正在出售第" + tickets-- +"张票!");
} else {
break;
}
}
}
}
-
在执行代码时候,会发现多个线程会卖出同一张票。这样会产生线程安全问题。
-
线程安全产生原因:1.具备多线程。2.操作共享数据。3.操作共享数据的代码有多条。
-
通过加锁:让每一时刻只能有一个线程操作数据。方式有三种
1.同步代码块
synchronized(锁对象){
容易产生线程安全问题的代码
}
// 锁对象:可以是任意对象,但是必须保证多个线程使用是同一个对象。
- 示例:
- 关键点:锁对象选择
public class SellTicketsDemo {
public static void main(String[] args) {
Tickets t = new Tickets();
Thread th1 = new Thread(t);
Thread th2 = new Thread(t);
Thread th3 = new Thread(t);
th1.start();
th2.start();
th3.start();
}
}
class Tickets implements Runnable{
static int tickets = 100;
// 如果o方法run方法里,则无法实现线程安全,原因是执行run方法,实现3个o对象,对于这三个线程来说o对象不是共有的同一个对象。
Object o = new Object();
public void run() {
while (true) {
synchronized (o) {// o所在的类被new几次
if (tickets> 0) {
try {
Thread.sleep(30);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "====" + "正在出售第" + tickets-- +"张票!");
} else {
break;
}
}
}
}
}
- 锁对象选取错误示例:
- Tickets2被new了3次导致,没有锁住。
public class SellTicketsDemo2 {
public static void main(String[] args) {
Tickets2 th1 = new Tickets2();
Tickets2 th2 = new Tickets2();
Tickets2 th3 = new Tickets2();
th1.start();
th2.start();
th3.start();
}
}
class Tickets2 extends Thread{
static int tickets = 100;
Object o = new Object();
public void run() {
while (true) {
synchronized (o) {
if (tickets> 0) {
try {
Thread.sleep(30);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "====" + "正在出售第" + tickets-- +"张票!");
} else {
break;
}
}
}
}
}
- 方式1:解决方式通过:构造方法解决:
public class SellTicketsDemo2 {
public static void main(String[] args) {
Object o = new Object();
Tickets2 th1 = new Tickets2(o);
Tickets2 th2 = new Tickets2(o);
Tickets2 th3 = new Tickets2(o);
th1.start();
th2.start();
th3.start();
}
}
class Tickets2 extends Thread{
static int tickets = 100;
Object o;
// 构造方法
public Tickets2(Object o) {
this.o=o;
}
public void run() {
while (true) {
synchronized (o) {
if (tickets> 0) {
try {
Thread.sleep(30);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "====" + "正在出售第" + tickets-- +"张票!");
} else {
break;
}
}
}
}
}
- 方式2:使用静态方式:
Static Object o = new Object();
- 方式3:使用字符串的常量
synchronized ("abc")
- 方式4:使用Class对象
synchronized (SellTicketsDemo2.class)
// 在jvm中,每一个类的Class总共只有一个
2.同步方法
- 把synchronized放到方法的修饰符中,锁的是整个方法。
public class SellTicketsDemo3 {
public static void main(String[] args) {
Tickets3 th1 = new Tickets3();
new Thread(th1).start();
new Thread(th1).start();
new Thread(th1).start();
}
}
class Tickets3 implements Runnable{
static int tickets = 100;
// 默认锁对象:this
public synchronized void run() {
while (true) {
if (tickets> 0) {
try {
Thread.sleep(30);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "====" + "正在出售第" + tickets-- +"张票!");
} else {
break;
}
}
}
}
上述代码虽然解决了线程安全问题,但是编程了单线程程序,原因synchronized锁的范围太大,第一个进来线程,执行完整个while循环。导致在使用同步方法时候也需要注意这个问题。
- 但可以通过同步方法解决懒汉式单例模式:
package com.xjk;
// 懒汉式:存在问题,存在线程安全问题
public class Singleton2 {
private static Singleton2 s;
// 构造方法私有化,为了不让别人随便new
private Singleton2() {
}
// 通过synchronized 解决线程安全问题
public synchronized static Singleton2 getInstance() {
if (s == null) {
s = new Singleton2();
}
return s;
}
}
// 当启100个线程,当一个线程执行到s=new Singleton2();此时刚要new Singleton2,cpu切到第二个线程,因为s此时还是等于null,第二个线程也执行new Singleton2() 导出单例模式创建多个对象。此时通过同步方法添加synchronized可以有效解决此问题。
- 同样StringBuffer是线程安全的内部有synchronized,效率相对StringBuilder低,StringBuilder是线程不安全的。
3.Lock
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class SellTicketsDemo4 {
public static void main(String[] args) {
Tickets4 th1 = new Tickets4();
new Thread(th1).start();
new Thread(th1).start();
new Thread(th1).start();
}
}
class Tickets4 implements Runnable{
static int tickets = 100;
// 也要保证该锁对象对于多个线程是同一个
Lock lock = new ReentrantLock();
public synchronized void run() {
while (true) {
lock.lock();// 加锁
try {
if (tickets> 0) {
try {
Thread.sleep(30);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "====" + "正在出售第" + tickets-- +"张票!");
} else {
break;
}
} finally {
lock.unlock();//解锁
}
}
}
}
- 释放锁的代码放到finally代码块中,否则容易造成程序阻塞。
6.死锁
- 是指两个或两个以上的线程在执行的过程中,因争夺资源产生一种互相等待现象。
7.线程池用法
-
线程池可以减少创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。
-
可以根据系统的承受能力,调整线程池中工作线程数目,放置因为消耗过多的内存,而把服务器累死(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)。
-
线程池的创建:
public static ExecutorService newCachedThreadPool() 创建一个具有缓存功能的线程池 public static ExecutorService new FixedThreadPool(int nThreads) 创建一个可重用的,具有固定线程数的线程池 public static ExecutorService newSingleThreadExecutor() 创建一个只有单线程的线程池,相当于上个方法的参数是1
-
示例:
import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class ThreadPoolDemo { public static void main(String[] args) { // 1 ExecutorService pool = Executors.newCachedThreadPool(); pool.execute(new Runnable() { public void run() { System.out.println("hello world"); } }); // 关闭线程池 pool.shutdown(); // 2 pool = Executors.newFixedThreadPool(20); } } // 缓存线程池,如果没有任务会等待60s就关闭