1.程序、进程、线程
进程可以细化为多个线程;每个线程拥有自己独立的:栈、程序计数器;多个线程共享一个进程中的结构:方法区、堆
2.单核cpu、多核cpu
单核cpu:是一种假的多线程;多核cpu,能够更好的发挥多线程的效率
一个java.exe至少有3个线程:main()主线程、gc()垃圾回收线程、异常处理线程
3.并行、并发
并行:多个cpu同时执行多个任务
并发:一个cpu同时执行多个任务
4.创建线程的方式
(1)创建线程的方式一
/*
*创建多线程步骤:
* 1.继承Thread类
* 2.重写THread类的run方法--》将线程执行的操作声明在run()中
* 3.创建Thread的子类的对象
* 4.使用子类调用start()方法
*
* */
class MyThread extends Thread{
@Override
public void run() {
for(int i = 0;i < 20;i++){
if(i % 2 == 0){
System.out.println(Thread.currentThread().getName() +"...."+ i);
}
}
}
}
public static void main(String[] args) {
MyThread thread_01 = new MyThread();
//thread_01线程中执行
thread_01.start();
}
使用匿名类创建多个线程
public static void main(String[] args) {
//new Thread().start();//此种方式并不是创建匿名的thread对象,而是开启Thread这个父类线程
new Thread(){//创建匿名子类线程1
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "....");
}
}.start();
new Thread(){//创建匿名子类线程2
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "....");
}
}.start();
}
Thread中常用的方法
1.设置name
方式1.setName()
MyThread thread_01 = new MyThread();
thread_01.setName("xiancheng");
方式2.构造器中调用父类构造器
MyThread thread_01 = new MyThread("xian");
class MyThread extends Thread{
public MyThread(String name){
super(name);
}
2.yield:线程让步,即释放当前cpu执行权
3.join:在线程a中调用线程b的join(),此时线程a就进入阻塞状态,直到线程b完全执行完以后,线程a才结束阻塞状态
4.sleep(time):让当前线程睡眠指定毫秒,在指定时间内,当前线程是阻塞状态
5.isAlive:判断当前线程是否存活
线程的调度
线程优先级:
1.获取线程优先级:thread_01.getPriority();
2.设置线程优先级: MAX_PRIORITY=10;MIN_PRIORITY=1,NORM_PRIORITY=5(默认值)
thread_01.setPriority(Thread.MAX_PRIORITY);
同优先级线程:先到先服务
对高优先级:高优先级的线程抢占cpu,也有可能抢不到,只是概率高而已
(2)创建线程的方式2:实现Runnable接口
/*
* 方式二创建多线程
* 1.实现Runnable接口
* 2.重写run方法
* 3.创建实现类的对象
* 4.将此对象作为参数,传递到Thread类的构造器中
* 5.使用Thread类对象调用start方法
* */
public class Thread_02 {
public static void main(String[] args) {
//创建实现类对象
MyThread_02 my = new MyThread_02();
//将对象作为参数,放入Thread类的构造器中
Thread t = new Thread(my);
//使用Thread类的对象调用start
t.start();
}
}
class MyThread_02 implements Runnable{
@Override
public void run() {
for(int i = 0;i < 100;i++){
if(i % 2 == 0){
// System.out.println(getName()+".........");不能直接getName,原因是该类并没有继承Thread类
System.out.println(Thread.currentThread().getName()+"........."+i);
}
}
}
}
售票:
/*
* 创建3个卖票窗口,一共有100张票
* 存在线程安全问题
* */
//实现接口方式
public class WindowTest_02 {
public static void main(String[] args) {
//创建1个Window_02
Window_02 w = new Window_02();
Thread t1 = new Thread(w);//不同线程使用同一个对象
Thread t2 = new Thread(w);
Thread t3 = new Thread(w);
//保证任意一个窗口售票时,其他窗口不运行,且获得公有的对象,即static
t1.setName("1号");
t2.setName("2号");
t3.setName("3号");
t1.start();
t2.start();
t3.start();
}
}
class Window_02 implements Runnable {
private int i = 100;//未使用static方法
......
}
//继承方式
public class WindowTest {
public static void main(String[] args) {
//创建3个卖票窗口
Window w1 = new Window();
Window w2 = new Window();
Window w3 = new Window();
//保证任意一个窗口售票时,其他窗口不运行,且获得公有的对象,即static
w1.setName("1号");
w2.setName("2号");
w3.setName("3号");
w1.start();
w2.start();
w3.start();
}
}
class Window extends Thread {
private static int i = 100;
.......
}
比较线程的两种方式
开发中,优先选择实现Runnable接口的方式
原因:1.实现方式没有单继承的局限性
2.实现的方式更适合处理多个线程有共享数据的情况
联系:public class Thread implements Runnable
相同点:两种方式都要重写run方法
线程通信:
wait()、notify()、notifyAll():此三种方法定义在Object类中
线程分类:守护线程、分类线程
线程的生命周期
线程同步,解决线程安全
1.以上述售票为例,会出现的问题:重票、错票---》即线程安全问题
2.问题出现原因:当某个线程还未完成操作时,有新的线程进入,也对其进行操作
3.解决办法:当某个线程还未完成操作时,其他线程必须在外等待,知道该线程完成操作才其它线程才可以运行。这种情况即使在线程出现阻塞时,也不能被改变
4.在java中,使用同步机制,解决线程安全问题:
(1)同步代码块:要求多个线程必须共用同一把锁;坏处,相当于单线程,效率低
sychronized(同步监视器){
//需要被同步的代码,不能多也不能少,即操作共享数据(多个线程共同操作的变量)的代码
}
同步监视器:俗称锁。任何一个对象都可以充当锁
1).实现Runnable接口方式的锁
class Window_02 implements Runnable {
private int i = 100;//未使用static方法
Object obj = new Object();//所有线程共享一个
@Override
public void run() {
while(true){
synchronized(obj){//锁;
synchronized(this){//此时的this,表示当前Window_02对象,implements Runnable接口时,多个线程公用一个线程子类对象
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(i > 0){
System.out.println(Thread.currentThread().getName()+"卖票:票号为"+i);
i--;
}else{
break;
}
}
}
}
2).继承Thread方式的锁
class Window extends Thread {
private static int i = 100;//必须使用static
private static Object obj = new Object();//必须使用static
@Override
public void run() {
while(true){
synchronized(obj){//正确
synchronized(Window.class){//正确,以类为对象,只会加载一次,即Class clazz = Window.class
//synchronized(this){//错误,因为extends Thread时,会创建多个线程对象,即,t1,t2,t3,他们不共用一个线程
......
}
}
}
}
(2)同步方法
如果操作共享数据的代码完整的声明在一个方法中,则将此方法声明为同步的,此方法为同步方法
总结:同步方法不需要显示的声明;非静态同步方法,同步监视器是this,静态同步方法,同步监视器是当前类本身
1)继承Thread
class Window extends Thread {
private static int i = 100;
@Override
public void run() {
show();
}
private static synchronized void show() {//使用static关键字修饰该锁,则所有成员共享同一个锁;该锁是当前类Window.class
// private synchronized void show() {//错误,此锁代表this,但创建的子类Thread对象创建了好几个,表示不同对象
while(true){
if(i > 0){
System.out.println(Thread.currentThread().getName()+"卖票:票号为"+i);
i--;
}else{
break;
}
}
}
}
2)实现Runnable接口
class Window_02 implements Runnable {
private int i = 100;//未使用static方法
@Override
public void run() {
show();
}
private synchronized void show() {//此时锁代表this
while(true){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(i > 0){
System.out.println(Thread.currentThread().getName()+"卖票:票号为"+i);
i--;
}else{
break;
}
}
}
}
死锁
1.不同线程分别占用对方需要的同步资源不放弃,都在等待对方释放资源;出现死锁后,不会出现异常和提示,只是所有线程都阻塞,无法继续
2.解决办法:尽量避免同步或者使用算法
5.lock锁解决线程安全(jdk5.0新特性)
同步锁使用Lock充当对象;使用ReentranLock的对象实现Lock
class MyLock extends Thread {
private static int i = 40;
private ReentrantLock lock = new ReentrantLock();//创建lock锁对象
@Override
public void run() {
try {
//打开锁
lock.lock();
while (true) {
if (i > 0) {
System.out.println(getName() + "...." + i);
i--;
} else {//跳出来,否则程序一直在运行
break;
}
}
}finally {
//释放锁
lock.unlock();
}
}
}
synchronized和lock异同:
相同点:都能解决线程安全问题
不同点:synchronized机制在执行完相应的同步代码块后,自动释放同步监视器;lock需要手动启动(lock)或结束(unlock)同步;lock只有代码块锁,synchronized有代码块锁和方法锁
优先使用顺序:lock--》同步代码块--》同步方法
例题:
银行有一个账户,有两个用户分别向同一个账户存3000元,每次存1000,存3次。每次存完打印账户余额
public class Bank {
public static void main(String[] args) {
//此种方式类似于实现runnable接口的创建调用start方法
Account acct = new Account(0);
Customer c1 = new Customer(acct);//创建名为甲的客户
Customer c2 = new Customer(acct);
c1.start();
c2.start();
}
}
//存储用户,每个用户共存款3次
class Customer extends Thread {
private Account acct;//创建公有的Account账户
//创建Account的构造方法
public Customer(Account acct) {
this.acct = acct;
}
@Override
public void run() {
for (int i = 1; i <= 3; i++) {
//调用Account存钱的方法
acct.deposit(1000);
}
}
}
//银行账户,共存到6000,即balance是共有数据
class Account {
private int balance = 0;
//创建Account的构造器,即一创建Account账户,便初始化其balance
Account(int balance){
this.balance = balance;
}
//方式一:lock
锁
private static ReentrantLock lock = new ReentrantLock();
void deposit(int money){
try{
lock.lock();
if(money >= 0){
balance += money;
System.out.println(Thread.currentThread().getName() + "存款,共存" + balance);
}
}finally {
lock.unlock();
}
}
//方式二,同步方法
synchronized void deposit(int money){
if(money >= 0){
balance += money;
System.out.println(Thread.currentThread().getName() + "存款,共存" + balance);
}
}
}
线程通信
wait()、notify()/notifyAll()必须搭配使用,且都属于Object方法;必须使用在同步代码块或同步方法中;调用者必须是同步代码块或同步方法中的同步监视器,否则出现IllegalMonitorStateException
wait:一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器
notify:唤醒被wait的一个线程,若有多个线程,则唤醒优先级高的
notifyAll:唤醒所有wait的线程
/*
* 使用线程1,2交替打印1~100的数
* */
public class Communication {
public static void main(String[] args) {
new Number().start();
new Number().start();
}
}
class Number extends Thread{
private static int num = 1;
private static Object obj = new Object();
@Override
public void run() {
while(true){
//继承方式的不能使用this,因为每次this的对象不同
synchronized (obj){
obj.notify();
if(num <= 20){
System.out.println(getName() + "打印" + num);
num++;
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else{
break;
}
}
}
}
}
例题:
sleep和wait的异同:
相同点:都能使当前线程进入阻塞状态
不同点:(1)位置不同,Thread类中声明sleep,Object类中声明wait
(2)调用要求不同,sleep可以在任意场景,wait必须在同步代码块或同步方法中
(3)是否释放同步监视器,若两者都使用在同步代码块或同步方法中,sleep不会释放锁,wait会释放锁
/*
* 线程通信的应用:生产者/消费者问题(经典例题)
* 生产者(Product)将产品交给店员(Clerk),而消费者(Customer)从店员处取走产品,店员一次只能持有固定数量的产品(如:20)
* 如果生产者试图生产更多的产品,店员会让生产者停止,若店中有空位再通知生产者继续生产;如果店中没有产品,店员会让消费者
* 等一下,若有产品了再通知消费者取走产品
*
* 分析:
* 1.多线程:生产者、消费者
* 2.线程安全:共享数据,产品或店主
* 3.解决线程安全:同步(3种)
* 4.线程间相互通信:无产品,无空位时
*
* */
public class ProductList {
public static void main(String[] args) {
Clerk clerk = new Clerk();
Productor p = new Productor(clerk);
Thread t = new Thread(p);
Customer_P c = new Customer_P(clerk);
t.setName("生产");
c.setName("顾客");
t.start();
c.start();
}
}
//共享数据
class Clerk{
private int num = 0;
//消费者消费产品
public synchronized void cust_Product() {//解决同步问题
if(num > 0){
System.out.println(Thread.currentThread().getName() + "..."+num);
num--;
notify();//消费者消费产品后,就唤醒生产者
}else {
try {
wait();//当没有产品,消费者处于等待状态
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//生产者生产产品
public synchronized void pro_Product() {//解决同步问题
if(num <= 20){
num++;
System.out.println(Thread.currentThread().getName() + "...."+num);
notify();//生产了产品,就唤醒消费者
}else {
try {
wait();//当产品多于20,生产者停止生产
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//implements Runnable设置Productor线程
class Productor implements Runnable{
private Clerk clerk;
Productor(Clerk clerk){
this.clerk = clerk;
}
@Override
public void run() {
while (true){
clerk.pro_Product();
}
}
}
//extends Thread设置Customer线程
class Customer_P extends Thread{
private Clerk clerk;
Customer_P(Clerk clerk){
this.clerk = clerk;
}
@Override
public void run() {
while (true){
clerk.cust_Product();
}
}
}
jdk5.0新增线程创建方式
新增方式1:实现Callable接口
与实现Runnable接口相比较,Callable功能更强大
(1)相比run方法,可以有返回值
(2)方法可以抛出异常
(3)支持泛型返回值
(4)需要借助FutureTask类,比如获得返回结果
/*
* 创建多线程方式3:实现Callable接口
* 1.实现Callable接口
* 2.重写call方法
* 3.创建接口实现类的对象
* 4.将该对象传入FutureTask的构造器中
* 5.将FutureTask对象传递到Thread类的构造器中
* 6.使用该对象的get方法得到重写call的返回值
*
* */
public class Callable_01 {
public static void main(String[] args) {
//3.创建接口实现类的对象
NumThread num = new NumThread();
//4.将接口实现类的对象作为参数传递到FutureTask构造器中,创建FutureTask的对象
FutureTask task = new FutureTask(num);
//5.将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start方法
new Thread(task).start();
try {
//6.获得FutureTask构造器参数Callable实现类的重写方法call的返回值
Object o = task.get();
System.out.println("总和为" + o);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
class NumThread implements Callable{
private int sum = 0;
@Override
public Object call() throws Exception {
for (int i = 0; i <= 100; i++) {
if(i % 2 == 0) {
sum += i;
System.out.println( sum);
}
}
return sum;
}
}
新增方式2:使用线程池(开发中常用)
好处:响应速度提高;提高资源重用率;便于管理
public class ThreadPool {
public static void main(String[] args) {
//1.提供指定数量的线程池
ExecutorService service = Executors.newFixedThreadPool(5);//创建线程的个数
//ExecutorService是一个接口,使用子类ThreadPoolExecutor可以调用其属性方法
// ThreadPoolExecutor service1= (ThreadPoolExecutor) service;
// service1.getMaximumPoolSize();
System.out.println(service.getClass());
//2.执行指定线程的操作,需要提供Runnable或Callable接口
service.execute(new NumPool());//适合Runnable接口
//service.submit(new Callable);//适合Callable接口
//3.关闭线程池
service.shutdown();
}
}