一、程序
是为完成特定任务、用某种语言编写的一组指令的集合。指一段静态的代码,是静态对象。
二、进程
进程是程序(任务)的一次执行过程,是一个动态的过程,有它自身的产生、存在和消亡的过程。进程持有资源(共享内存,共享文件)和线程。进程作为资源分配的单位,系统在运行时会为每个进程分配不同的内存区域。
三、线程
线程是系统中最小的执行单元。同一进程中有多个线程,线程是一个程序内部的一条执行路径。
线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器(pc)。线程切换的开销小。
一个进程中的多个线程共享相同的内存单元/内存地址空间(堆,方法区)。他们从同一堆中分配对象,可以访问相同的变量和对象,这就使得线程间通信更简便、高效。但多个线程操作共享的系统资源可能会带来安全的隐患。
线程的交互包括互斥(竞争资源)、同步
一个java应用程序java.exe,至少有三个线程:main()主线程,gc()垃圾回收线程、异常处理线程。当然如果发生异常,会影响主线程。
四、并行与并发
并行:多个cpu同时执行多个任务
并发:一个cpu(采用时间片)同时执行多个任务,比如:秒杀、多个人做同一件事
五、Java对线程的支持
java.lang包提供了Thread类和Runnable接口,这两个都有一个public void run()方法。这个方法为我们提供了线程实际执行工作的代码。start()方法:①启动线程,②调用线程的run()方法
六、Thread常用方法
long millis的单位是毫秒,也就是需要等待或睡眠的最长时间。nanos可以精确到微秒
下面的方法是Thread的对象的方法
还有一些Thread的静态方法,也就是不需要实例化对象,可以通过Thread类直接调用的方法
七、线程优先级的设置
线程的调度:
1、时间片:每个线程轮流执行时间片时间
2、抢占式:高优先级的线程抢占cpu
Java的调度方法:
同优先级线程组成先进先出队列(先到先服务),使用时间片策略。对于高优先级,使用优先调度的抢占式策略
线程的优先级等级:
MAX_PRIORITY:10 MIN_PRIORITY:1 NORM_PRIORITY:5(默认)
涉及的方法:
getPriority():返回线程优先级
setPriority(int newPriority):改变线程的优先级
说明:
线程创建时继承父线程的优先级。低优先级只是获得调度的概率低,并非一定是在高优先级之后才被调用
八、线程的创建
1、继承Thread类
步骤:新建一个类继承Thread类,重写run()方法,新建类的实例,实例.start()
2、实现Runnable接口
步骤:新建一个类实现Runnable接口,重写run()方法,新建这个类的实例,将这个实例作为参数传递给Thread,新建一个Thread对象,由Thread对象.start()
上述两种方式的比较:
1、开发中优先选择实现Runnable接口的方式。原因:①:实现的方式没有类的单继承②:实现的方式更适合来处理多个线程有共享数据的情况(只需要创建一个实现Runnable接口的类的对象,然后将这个对象当做参数传递给多个不同的Thread)
2、联系:pyublic class Thread implements Runnable
3、两种方式都需要重写run()方法,将线程要执行的逻辑声明在run()中
线程通信:wait()/notify()/notifyAll():此三个方法定义在object类中
线程分类:分为用户线程和守护线程,守护线程是用来服务用户线程的
线程的生命周期
3、实现Callable接口
与使用Runnable相比,Callable功能更强大些。
(1)相比Runnable的run()方法,Callable的call()方法可以有返回值
(2)方法可以抛出异常
(3)支持泛型的返回值
(4)需要借助FutureTask类,比如获取返回结果 。Future接口可以对具体Runnable、Callable任务的执行结果进行取消、查询是否完成、获取结果等。FutureTask是Future接口的唯一实现类。FutureTask同时实现了Runnable、Future接口,它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值。
九、线程安全问题及解决
例子:创建是三个窗口卖票,总票数100张。使用实现Runnable接口的方式
问题1:卖票过程中,出现了重票、错票,出现了线程安全问题
问题出现的原因:当某个线程操作车票的过程中,尚未操作完成时,其他线程参与进来,也操作车票
解决:当一个线程a在操作车票的时候,其他线程不能参与进来。直到线程a操作完之后其他线程才可以开始操作,这种情况即使a出现了阻塞,也不能被改变。在Java中,通过同步机制来解决线程的安全问题。
方式一:同步代码块
synchronized(同步监视器){
//需要被同步的代码
}
说明:操作共享数据的代码,即为需要被同步的代码。
共享数据:多个线程共同操作的变量
同步监视器:锁,任何一个类的对象都可以充当锁(要求:多个线程必须同用一把锁)
使用继承Thread的方式实现
package duoxianceng;
public class Thread100 extends Thread{
private static int i=100; //必须是static,保证多个实例操作的是同一个变量
static Object obj=new Object(); //必须是static,要保证锁是唯一的
@Override
public void run() {
while(true){
synchronized (Thread100.class){//Thread100.class得到类Thread100的对象,类的对象是惟一的
//synchronized(obj){
if(i>0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("分线程"+getName()+" "+i);
i--;
}
}
}
}
public static void main(String[] args) {
Thread100 t1=new Thread100();
t1.setName("窗口一");
Thread100 t2=new Thread100();
t2.setName("窗口二");
Thread100 t3=new Thread100();
t3.setName("窗口三");
t1.start();
t2.start();
t3.start();
}
}
使用实现Runnable的方式实现
package duoxianceng;
public class Runna100 implements Runnable{
Object obj=new Object();//为了保证锁是唯一的,不能定义在函数体内
int i=100;//为了保证多个线程操作的是同一个i
@Override
public void run() {
//在实现Runnable接口创建多线程的方式中,可以采用this充当同步监视器
while (true) { //while不能被包含在synchronized里面,放在里面就相当于一个线程拿着锁一顿while循环,i>0退出之后已经无法执行了,其他线程没有工作
synchronized (this){
//synchronized(obj) {
if (i > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("分线程" + Thread.currentThread().getName() + " " + i);
i--;
}
}
}
}
public static void main(String[] args) {
Runna100 r1=new Runna100();
Thread t1=new Thread(r1,"窗口1");
Thread t2=new Thread(r1,"窗口2");
Thread t3=new Thread(r1,"窗口3");
t1.start();
t2.start();
t3.start();
}
}
方式二:同步方法
同步方法仍然涉及到同步监视器,只是不需要我们显示的声明
非静态的同步方法,同步监视器:this。静态的同步方法,同步监视器:当前类本身
继承Thread的同步方法
package duoxianceng;
public class Threadfunc extends Thread{
static int i=100;
public void run(){
while(true){
show();
}
}
static synchronized void show(){// 该方法必须是static,将同步的操作单独成一个方法,同步监视器:Threadfunc.class
if(i>0){
System.out.println(Thread.currentThread().getName()+i);
i--;
}
}
public static void main(String[] args) {
Threadfunc t1=new Threadfunc();
t1.setName("窗口一");
Threadfunc t2=new Threadfunc();
t2.setName("窗口二");
Threadfunc t3=new Threadfunc();
t3.setName("窗口三");
t1.start();
t2.start();
t3.start();
}
}
实现Runnable的同步方法
package duoxianceng;
public class Runnafunc implements Runnable{
int i=100;
@Override
public void run() {
while(true){
show();
}
}
public synchronized void show(){//同步监视器:this
if(i>0){
System.out.println(Thread.currentThread().getName()+i);
i--;
}
}
public static void main(String[] args) {
Runnafunc rf=new Runnafunc();
Thread t1=new Thread(rf,"线程1");
Thread t2=new Thread(rf,"线程2");
Thread t3=new Thread(rf,"线程3");
t1.start();
t2.start();
t3.start();
}
}
方法三、lock
package duoxianceng;
import java.util.concurrent.locks.ReentrantLock;
public class LockTry implements Runnable{
private int ticket=100;
//1.实例化ReentrantLock
private ReentrantLock lock=new ReentrantLock();
@Override
public void run() {
while(true){//while要放在最外面,不能放在try内部,那样的话就只能有一个线程在运行了
try{
//2.调用lock方法
lock.lock();
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(ticket>0){
System.out.println(Thread.currentThread().getName()+ticket);
ticket--;
}else{
break;
}
}finally {
//3.调用解锁方法
lock.unlock();
}
}
}
public static void main(String[] args) {
LockTry lt=new LockTry();
Thread t1=new Thread(lt,"线程1");
Thread t2=new Thread(lt,"线程2");
Thread t3=new Thread(lt,"线程3");
t1.start();
t2.start();
t3.start();
}
}
synchronized与Lock对比
1、Lock是显示锁(手动开启和关闭锁,别忘记关闭锁)synchronized是隐式锁,除了作用域会自动释放
2、lock只有代码块锁,synchronized有代码块锁和方法锁
3、使用lock锁,JVM将花费较少的时间来调度线程,性能更好,并且具有更好的可扩展性
优先使用顺序:lock--->同步代码块----->同步方法
十、线程死锁问题
不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁。
出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续
解决方法:专门的算法、原则。尽量减少同步资源的定义。尽量避免嵌套同步。
例题1:
银行有一个账户,有两个储户分别向同一个账户存3000元。每次存1000,存3次。每次存完打印账户余额。
package duoxianceng;
class Account{
public double balance=0; //账户余额
public Account(double balance) {
this.balance = balance;
}
//存钱的方法
public synchronized void AddMoney(double m){
if(m>0){
balance+=m;
System.out.println(Thread.currentThread().getName()+"存钱成功,余额为:"+balance);
}
}
}
class Customer extends Thread{
private Account account; //保证两个储户操作的是同一个账户
public Customer(Account t){
this.account=t;
} //保证两个储户操作的是同一个账户
@Override
public void run() {
for(int i=0;i<3;i++){
account.AddMoney(1000);
}
}
}
public class Bank {
public static void main(String[] args) {
Account a=new Account(0);
Customer c=new Customer(a);
Customer c2=new Customer(a);
c.setName("甲");
c2.setName("乙");
c.start();
c2.start();
}
}
例题2(线程通信):
使用两个线程打印1-100,线程1和线程2交替打印。
涉及到的三个方法:
wait():一旦执行此方法,当前线程进入阻塞状态,并释放同步监视器
notify():一旦执行此方法,就会唤醒被wait的一个线程,如果有多个线程被wait,就唤醒多个线程中优先级最高的
notifyAll():一旦执行此方法,就会唤醒所有被wait的线程
注意:1、wait(),notify(),notifyAll()三个方法必须使用在同步代码块或同步方法中
2、wait(),notify(),notifyAll()三个方法的调用者必须是同步代码块或同步方法中的同步监视器,否则会异常
3、wait(),notify(),notifyAll()三个方法是定义在java.lang.Object中
package duoxianceng;
public class Xianchengtongxin implements Runnable{
private int num=0;
private Object obj=new Object();
@Override
public void run() {
while(true){
synchronized (obj){
//synchronized (this){//this是类Xianchengtongxin的对象,作为同步监视器,必须唯一
//唤醒线程
obj.notify();
//this.notify();
if(num<=100){
try {
//sleep()会使线程阻塞,但他不会释放同步监视器
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"---"+num);
num++;
//使得调用如下wait()方法的线程进入阻塞状态,线程会释放同步监视器
try {
obj.wait();
//this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else{
break;
}
}
}
}
public static void main(String[] args) {
Xianchengtongxin x=new Xianchengtongxin();
Thread t1=new Thread(x,"线程1");
Thread t2=new Thread(x,"线程2");
t1.start();
t2.start();
}
}
sleep()和wait()的异同?
1、相同点:一旦执行方法,都可以使得当前的线程进入阻塞状态
2、不同点:<1>两个方法声明的位置不同,Thread类中声明sleep(),Object类中声明wait()
<2>调用的要求不同:sleep()可以在任何需要的场景下调用,wait()必须使用在同步代码块或同步方法中
<3>关于是否释放同步监视器:如果两个方法都使用在同步方法或同步代码块中,sleep()不会释放同步监视器,wait()会释放同步监视器。
例题3(线程通信)
生产者/消费者问题
生产者(Productor)将产品交给店员(Clerk),而消费者(Customer)从店员处取走产品,店员一次只能持有固定数量的产品(比如20),如果生产者试图生产更多的产品,店员会叫生产者停一下,如果店中有空位放产品了再通知生产者继续生产,如果店中没有产品,店员会告诉消费者等一下,如果店中有产品了再通知消费者取走产品。
package duoxianceng;
class Clerk{
private int product=0;
public synchronized void produceProduct() {
this.notify();//用来唤醒消费者线程
if(product<20){
product++;
System.out.println(Thread.currentThread().getName()+"正在生产第"+product+"个产品");
}else{
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public synchronized void consumeProduct() {
if(product>0){
System.out.println(Thread.currentThread().getName()+"正在消费第"+product+"个产品");
product--;
notify();//用来唤醒生产者线程
}else{
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class Producer extends Thread{
private Clerk cle;
public Producer(Clerk cle) {
this.cle = cle;
}
@Override
public void run() {
System.out.println(getName()+"开始生产产品");
while(true){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
cle.produceProduct();
}
}
}
class Consumer extends Thread{
private Clerk cle;
public Consumer(Clerk cle) {
this.cle = cle;
}
@Override
public void run() {
System.out.println(getName()+"开始消费产品");
while(true){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
cle.consumeProduct();
}
}
}
public class Productor {
public static void main(String[] args) {
Clerk cl=new Clerk();
Producer p=new Producer(cl);
Consumer c1=new Consumer(cl);
p.start();
c1.start();
}
}