多线程
单核CPU,以单个线程完成多个任务会比多个线程完成的要快,因为单核CPU要是有多个线程的话CPU要切换
何时需要多线程
- 程序需要同时执行两个或多个任务
- 程序需要实现一些需要等待的人物,如用户输入,文件读写操作、搜索等
- 需要一些后台运行的程序
多线程的创建
1.创建一个继承于Thread类的子类
2.重写Thread的run方法,将此线程执行的操作声明在run方法中
3.创建Thread类的子类的对象
4.通过此对象调用start
多线程是存在交互性的
package ThreadT;
//1. 创建Thread的子类
class MyThread extends Thread{
// 2.重写run
public void run(){
for(int i = 0;i < 100; i++){
if(i % 2 == 0){
System.out.println(i);
}
}
}
}
public class ThreadTest {
public static void main(String[] args) {
// 3. 创建Thread子类的对象
MyThread t1 = new MyThread();
// 4.通过此对象调用start
t1.start();
for(int i = 0;i < 100; i++){
if(i % 2 == 0){
System.out.println(i + "+++++++++++++++");
}
}
}
}
i 和 i********** 会交互
start两个作用,1.启用当前线程。2.使用当前线程的run
不能让已经start的线程去start
线程的常用方法
1.start() 启动当前线程,调用当前的run
2.run() 通常需要重写Thread类中的此方法,将创建的线程要执行的操作声明在此方法中
3. currentThread 静态方法,返回执行当前代码的线程
4. getName:获取当前线程的名字
5. setName: 设置当前线程的名字
6. yield : 释放当前cpu的执行权(当前下一刻也能抢过来)
7. join:在线程A中调用线程B的join方法,线程A进入阻塞状态,直到线程B完全执行完以后,线程A才结束阻塞
8. stop 强行结束线程周期,不建议使用
9. sleep(long millitime) 休息一会 ,单位是毫秒,在指定的时间之内,当前线程是堵塞状态,都会报异常 try - catch 一下 倒计时啥的用一下
10.isAlive() : 判断当前线程是否还存活
package ThreadT;
class HelloThread extends Thread{
public void run(){
for(int i = 0;i < 100;i++){
if(i % 2 == 0){
// try {
// sleep(1000);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
System.out.println(Thread.currentThread().getName() + ":" + i);
}
if(i % 20 == 0){
this.yield(); // 释放当前CPU执行权
}
}
}
public HelloThread(String name){
super(name); // 也能这样给线程取名字
}
}
public class ThreadMethodTest {
public static void main(String[] args) {
HelloThread h1 = new HelloThread("线程一");
// h1.setName("线程一");
h1.start();
// 给主线程做个命名
Thread.currentThread().setName("主线程");
for(int i = 0;i < 100;i++){
if(i % 2 == 0){
System.out.println(Thread.currentThread().getName() + ":" + i);
}
if (i == 20){
try {
h1.join(); // h1过来执行完
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
System.out.println(h1.isAlive());
}
}
线程的优先值设置
高优先级的线程抢占CPU
线程的优先级等级
- MAX_PRIORITY : 10
- MIN_PRIORITY : 1
- NORM_PRIORITY : 5 默认的优先级
getPriotity() 可以返回线程优先级
setPriority(int newPriority) 改变线程的优先级
说明:
线程的创建时回继承父线程的优先级
低优先级只是获得调度的概率低,并非一定是在高优先级线程之后才被调用
创建多线程有两种方式
// 1.创建一个实现了Runnable接口的类
// 2.实现类去实现Runnable的抽象方法 run()
// 3.创建实现类的对象
// 4.将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
// 5.通过Thread类的对象调用start()
package ThreadT;
// 创建线程的方式2 实现Runnable 接口
//1.创建一个实现了Runnable接口的类
class MThread implements Runnable{
// 2.实现类去实现Runnable的抽象方法 run()
@Override
public void run() {
for (int i = 0;i < 100;i++){
if (i % 2 == 0){
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}
public class ThreadTest1 {
public static void main(String[] args) {
// 3.创建实现类的对象
MThread mThread = new MThread();
// 4.将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
Thread t1 = new Thread(mThread);
t1.start();
// 再启动一个线程,遍历100以内的偶数,可以不用给钱new MThread
Thread t2 = new Thread(mThread);
t2.start();
}
}
线程的生命周期
线程的安全问题
问题出现的原因:当某个线程操作车票的过程中,尚未完成操作时,其他线程参与进来,也操作共享数据
解决:当一个线程A在操作共享数据时,其他线程都不能参与进来,直到线程A完成操作,其他线程才可以开始操作,这样即使线程A出现了阻塞,也不能被改变
Java中通过同步机制,来解决线程的安全问题
方式一 : 同步代码块
synchranized(同步监视器){
// 需要被同步的代码
}
说明:
- 操作共享数据的代码,即位需要被同步的代码 --> 不能包含代码多了,也不能包含代码少了。
- 共享数据:多个线程共同操作的变量
- 同步监视器:俗称,锁。任何一个类的对象,都可以来充当锁。要求多个线程必须要用同一把锁。在创建类的时候就 Object obj = new Object(); 就造一个对象然后用Thread 去弄就不用在Object前加static 如果要造多个类的对象,就得 static Object。(接口的话一般就造一个继承Thread的对象)
类也是个对象,且类只会加载一次
同步的方式:安全了,但是变慢了。操作同步代码时,只能有一个线程参与,其他线程等待,相当于是一个单线程的过程,效率低
在继承Thread类创建多线程的方式中,慎用this来充当同步监视器,建议使用当前类来充当同步监视器
方式二:同步方法
如果操作共享数据的代码,正好完整的声明在一个方法中,我们不妨将此方法声明同步的。
public synchronized void ...() // 同步监视器:this
用接口创建这样是没问题,但是如果继承的话,
public static synchronized void show() // 同步监视器:当前类
1.关于同步方法的总结:同步方法仍然需要涉及到同步监视器,只是不需要我们显式的声明
2.非静态的同步方法,同步监视器是:this
静态的同步方法:同步监视器是当前类本身
使用同步机制将单例模式中的懒汉式改为线程安全的(之前提过)
package ThreadT;
// 使用同步机制将单例模式中的懒汉式改为线程安全的
public class BankTest {
}
class Bank{
private Bank(){}
private static Bank instance = null;
// 若有多个线程可能会一起进去,有线程安全问题
// 方式一:效率稍差
public static Bank getInstance(){
// synchronized (Bank.class){
// if (instance == null){
// instance = new Bank();
//
// }
// return instance;
// }
// }
// 方式二:效率高,后面的线程没必要进行同步代码块了
if(instance == null){
synchronized (Bank.class){
if (instance == null){
instance = new Bank();
}
}
}
return instance;
}
}
死锁
不同的线程分别占用对方需要的资源不放弃,都在等待对方放弃自己所需要的同步资源,就形成了线程的死锁
出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处理阻塞状态,无法继续
解决方法
- 专门的算法、原则
- 尽量减少同步资源的定义
- 尽量避免嵌套同步
我们使用同步时,要避免死锁
package ThreadT;
public class ThreadsisuoTest {
public static void main(String[] args) {
StringBuffer s1 = new StringBuffer();
StringBuffer s2 = new StringBuffer();
new Thread(){
public void run(){
synchronized(s1){
s1.append("a");
s2.append("1");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
synchronized(s2){
s1.append("b");
s2.append("2");
System.out.println(s1);
System.out.println(s2);
}
}
}
}.start();
new Thread(new Runnable(){
public void run(){
synchronized(s2){
s1.append("c");
s2.append("3");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
synchronized(s1){
s1.append("d");
s2.append("4");
System.out.println(s1);
System.out.println(s2);
}
}
}
}).start();
}
}
第一个线程进入的时候拿着s1的钥匙,sleep了一下,第二个线程就有更大的概率会启动(第一个线程不sleep第二个线程也可能会启动,就是让更大的可能让两个线程都停留在拿了第一个锁的位置),这时候第一个线程手上的钥匙是s1 ,第二个线程手上的钥匙是s2,但第一个线程想要出去又需要钥匙s2,第二个线程也需要钥匙s1,他两就会僵持不动
Lock(锁)
Lock 和 synchronized 的异同
-
相同点:都是用来解除线程安全问题
-
不同点:synchronized 机制在执行完相应的同步代码后,自动的释放同步监视器
Lock 需要手动的去启动同步(Lock()),同时结束同步也需要手动的实现(unlock())package ThreadT;
import java.util.concurrent.locks.ReentrantLock;
// Lock锁 – JKD5.0新增
public class LockTest {
public static void main(String[] args) {
Windows w = new Windows();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(); }
}
class Windows implements Runnable{
private int ticket = 100;
// 1.实例化一个Lock
private ReentrantLock lock = new ReentrantLock();@Override public void run() { while(true){ try{ // 2.调用lock lock.lock(); if(ticket > 0){ System.out.println(Thread.currentThread().getName() + ":, 售票,票号为 " + ticket); ticket --; }else{ break; } }finally{ //3.调用解锁方法 lock.unlock(); } } } }
建议解决线程安全问题时,最好用Lock 再同步代码块,其实都差不多
sleep 和 wait 方法的异同
相同点:一旦执行到sleep和wait,都可以使得当前的进程进入阻塞状态
不同点:1.两个方法声明的位置不同,Thread 类中声明sleep,Object类中声明wait
2.调用的要求不同:sleep()可以在任何需要的常见下调用,wait必须在使用同步代码块或同步方法中调用
3.关于是否释放同步监视器:如果两个方法都使用在同步方法块或者同步方法中,sleep方法不会释放锁,wait()会释放同步监视器(锁)
JDK5.0新增的创建线程的方式
新增方式一:实现Callable接口
与使用Runnable相比,Callable功能更强大
- 相比run方法,可以有返回值
- 方法可以抛出异常
- 支持泛型的返回值
- 需要借助FutureTask类,比如获取返回结果
package ThreadT;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
// 实现Callable接口 --- JDK 5.0新增
// 1.创建一个实现Callable接口的类
class NumThread implements Callable{
@Override
// 2.实现call方法,将此线程需要实现的操作声明在此方法中,并且可以有返回值
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) {
// 3.创建Callable接口实现类的对象
NumThread numThread = new NumThread();
// 4.将Callable实现类的对象作为参数传递到FutureTask构造器中,创建FutureTask对象
FutureTask futureTask = new FutureTask(numThread);
// 5.将FutureTask的对象作为参数传递到Thread的构造器中,创建Thread对象,最后start
new Thread(futureTask).start();
try {
// 6.需要的话得到返回值
// get方法返回值即为futureTask构造器参数Callable重写的call方法的返回值
Object sum = futureTask.get();
System.out.println("总和为" + sum);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
如何理解Callable 比 Runnable 好
- call() 有返回值
- call() 可以抛出异常,被外面的操作捕获,获得异常的信息
- Callable 支持泛型
线程池
package ThreadT;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class xianchengchi {
public static void main(String[] args) {
// 1.提供指定线程数量的线程池
ExecutorService service = Executors.newFixedThreadPool(10);
// 2.执行指定的线程操作,需要提供Runnable
service.execute(new NumberThread()); //适合Runnable
service.execute(new NumberThread1()); //适合Runnable
// 3.关闭线程池
service.shutdown();
// service.submit(task); //适合Callable
}
}
class NumberThread implements Runnable{
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{
public void run(){
for (int i = 0;i < 100;i++){
if(i % 2 != 0){
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}