基本概念
程序(program)
是为完成特定任务,用某种语言编写的一组指令的集合,一段静态的代码,是一个静态的东西
进程(process)
是程序的一次执行过程,或者说正在运行的一个程序。是一个动态的过程,有塔自身产生,存在,消亡的过程。进程的生命周期。
比如QQ,网易云音乐,这些是程序,一旦运行起来,就加载到内存中变成进程了
线程(thread)
线程是在进程里面的,是一个程序内部的一条执行路径。只有一线程,程序排队执行,如果有8线程,程序分为8队同时进行。每个线程拥有独立的运行栈和程序计数器。多线程可以共享进程的内存地址
线程可能有安全隐患。线程开的越多,cpu内存占用越多。
java应用程序至少有三个线程,main()主线程,gc()垃圾回收线程,异常处理线程
并行
并发
一个cpu同时执行多个任务,比如:限时秒杀,多个人同时做一件事,多个人好比多个线程
线程创建
1. 继承于Thread类
public class test1 {
public static void main(String[] args) {
//3. 创建Thread类的子类对象
MyThread t1 = new MyThread();
//4. 通过这个对象调用start方法
t1.start();//一个线程只能start一次,如果还要调用分线程,就在new一个线程对象
//上面的创建t1和调用start是主线程做的,但是t1是新开的线程,t1里面的run方法里面的代码是新的线程在做
//从start开始这时候t1和main一起执行
for(int i=0;i<10000;i++){
if(i%3==0)
System.out.println("****");
}
}
}
//1. 创建一个继承于Thread类的子类
class MyThread extends Thread{
//2. 重写Thread类的run方法,将此线程要执行的事写在run方法中
public void run() {
for(int i=0;i<10000;i++){
if(i%3==0)
System.out.println(i);
}
}
}
运行结果发现,不是先打印**,也不是先打印数字,两个循环一起执行,由于cpu算的太快,打印穿插起来了
2. 实现Runnable接口
public class test1 {
public static void main(String[] args) {
//3. 创建实现类对象
InterThread interThread = new InterThread();
//4. 将此对象作为参数传递到Thread构造器中,创建Tread对象
Thread thread = new Thread(interThread);
//5. 通过Thread对象调用start方法
thread.start();
}
}
//1. 创建一个实现了Runnable接口的类
class InterThread implements Runnable{
//2. 重写run方法
public void run() {
for(int i=0;i<100;i++){
System.out.println(i);
}
}
}
3. 实现Callable接口
public class test1 {
public static void main(String[] args) {
//3. 实现类实例化
myThread t1 = new myThread();
//4. 创建一个FutureTask对象,传入实现类对象,支持泛型,call返回什么类型,就写什么类型
FutureTask<Integer> futureTask = new FutureTask<Integer>(t1);
//5. 实例化Thread对象,传入futureTask对象,开始线程
new Thread(futureTask).start();
//如果对call方法的返回值不感兴趣,就不用调下面的get方法
try{
//6. 可以通过FutureTask对象的get方法获得线程方法的返回值
Object sum = futureTask.get();
System.out.println("总和:"+sum);
}catch (Exception e){
e.printStackTrace();
}
}
}
//1. 实现Callable接口
class myThread implements Callable{
int sum=0;
//2. 重写call方法,有返回值
@Override
public Object call() throws Exception {
for(int i =1;i<1000;i++){
System.out.println(i);
sum+=i;
}
return sum;
}
}
4. 线程池
经常创建销毁线程,对性能影响很大。
提前创建好多个线程,放入线程池中,使用时,直接获取,使用完放回池中,可以避免频繁创建销毁实现重复利用。还便于线程管理
public class test1 {
public static void main(String[] args) {
//Executors工具类,线程池的工厂类,用于创建并返回不同类型的线程池
//ExecutorService真正的线程池接口
//newFixedThreadPool创建一个可重用固定线程数的线程池
ExecutorService service = Executors.newFixedThreadPool(10);
//ThreadPoolExecutor是ExecutorService的子类,强转过来,tpe可以进行一系列的配置
ThreadPoolExecutor tpe = (ThreadPoolExecutor)service;
//执行线程操作,具体做什么还是要你自己创建
tpe.execute(new myThread());//适用于Runnable
tpe.execute(new myThread1());
//service.submit();//适用于Callable
//关闭线程池
tpe.shutdown();
}
}
class myThread implements Runnable{
@Override
public void run() {
for(int i =0;i<=1000;i++){
System.out.println(i);
}
}
}
class myThread1 implements Runnable{
@Override
public void run() {
for(int i =0;i<=100;i++){
System.out.println("哈哈哈哈哈"+i);
}
}
}
线程的常用方法
- Thread.currentThread():静态方法,返回当前执行当前代码的线程
- getName():获取当前线程名字
- setName():设置当前线程名字
- yield():一旦线程执行此方法,表示释放当前cpu执行,释放了下一次操作,有可能又被这个线程抢到了,有可能被别的线程拿到了
- join():在当前线程调用其他线程的join方法,当前线程停下来,等调用join方法的那个线程执行完了,当前线程才开始执行
- stop():已过时,当执行此方法,强制结束当前线程
- sleep():传入毫秒,休眠线程
- isAlive():判断线程是否还存活
线程优先级
同优先级线程组成先进先出队列,先到先服务,使用时间片策略
对高优先级,使用优先调度的抢占式策略,只是先执行的概率高
MAX_PRIORITY:10 最大优先级10 MIN_PRIORITY:1 最小优先级1 NORM_PRTORITY:5 默认优先级5
public class test1 {
public static void main(String[] args) {
Thread t1 = new MyThread();
Thread t2 = new MyThread1();
t1.setName("线程1号");
t2.setName("线程2号");
t1.setPriority(Thread.MIN_PRIORITY);
t2.setPriority(9);
t1.start();
t2.start();
}
}
//使用getPriority方法获取当前线程的优先级,默认是5
//setPriority设置优先级
class MyThread extends Thread{
public void run() {
for(int i=0;i<100;i++){
if(i%3==0)
System.out.println(this.getName()+i+"线程优先级:"+this.getPriority());
}
}
}
class MyThread1 extends Thread{
public void run() {
for(int i=0;i<100;i++){
if(i%3==0)
System.out.println(this.getName()+i+"线程优先级:"+this.getPriority());
}
}
}
线程的生命周期
线程同步
多线程操作同一个共享数据时,容易出差错。假如:1,2,3线程都同时进行money变量的操作,money本来有3000,1线程取钱2000,2线程取钱3000,如果1,2同时执行了,最后money变成负的。
所以需要在线程1操作共享数据时,其他线程不能参与进来,直到线程1操作完成,其他想线程才能开始操作共享数据,即使线程1被阻塞,也不能改变
在java中,通过同步机制解决线程安全问题
安全问题演示
在这段代码中,count是一个共享数据,应该是唯一的,3线程同步执行,每次都休眠20毫秒,这3个线程都在这20毫秒进入准备状态,同时执行,所以会出现重复数据,错误数据等情况
public class test1 {
public static void main(String[] args) {
//3. 创建实现类对象
InterThread interThread = new InterThread();
Thread thread = new Thread(interThread);
Thread thread2 = new Thread(interThread);
Thread thread3 = new Thread(interThread);
thread.start();
thread2.start();
thread3.start();
}
}
class InterThread implements Runnable{
int count = 100;
public void run() {
for(;count>=0;count--){
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":"+ count);
}
}
}
1. 同步代码块
class InterThread implements Runnable{
int count = 100;//共享数据
String a="同步锁,只要是个对象,什么对象什么内容都可以,多线程必须要用同一把锁";
public void run() {
while (true){
//在synchronized中传入同步锁,代码块中写要同步的代码
//不要包住循环,包住循环,就相当于第一个线程拿到锁,就要执行完整个循环才结束,结束循环后共享变量都用完了,其他的线程干瞪眼无作为
synchronized (a){
if(count>0){
System.out.println(Thread.currentThread().getName()+":"+ count);
count--;
}
else {
break;
}
}
}
}
}
2. 同步方法
class InterThread implements Runnable{
int count = 100;//共享数据
String a="同步锁,只要是个对象,什么对象什么内容都可以,多线程必须要用通一把锁";
public void run() {
while (true){
if(count>0){
show();
}
else {
break;
}
}
}
//同步方法
//非静态方法,同步监视器是this
//静态方法,同步监视器是当前类本身
public synchronized void show(){
if(count>0){
System.out.println(Thread.currentThread().getName()+":"+ count);
count--;
}
}
}
3. lock
class InterThread implements Runnable{
int count = 1000;//共享数据
//1. 实例化一个lock,可以传参数,是个bool类型,默认false,线程抢单,传入了true就让线程排队按顺序执行
private ReentrantLock lock =new ReentrantLock();
public void run() {
while (true){
try{
//2. 调用lock方法,用try catch finally,try把要同步的代码包起来,lock()方法个try这代码块上同步锁
lock.lock();
if(count>0){
System.out.println(Thread.currentThread().getName()+":"+ count);
count--;
}
else {
break;
}
}finally {
//没有异常就不用catch,一定要finally,因为要解锁
//3. 解锁,最后同步执行完成前面上锁了,后面就要把锁解开
lock.unlock();
}
}
}
}
线程死锁
不同的线程分别占用对方需要的同步资源不放弃,都在等对方放弃自己需要的同步资源,就形成了死锁。
出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续。
public class test1 {
public static void main(String[] args) {
StringBuffer s1 = new StringBuffer();
StringBuffer s2 = new StringBuffer();
//匿名的方式创建的线程
new Thread(){
public void run(){
//这个线程,先握住s1这把锁,然后又握住s2这把锁,两把锁在身上
synchronized (s1){
//线程1拿到了s1,然后被阻塞了100毫秒,计算机100毫秒可以做很多事,这100毫秒线程2可没闲着
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
s1.append("a");
s2.append("1");
//阻塞完毕了,线程1需要s2这把锁,但是被线程2拿了
synchronized (s2){
s1.append("b");
s2.append("2");
System.out.println(s1);
System.out.println(s2);
}
}
}
}.start();
//两种匿名的方式
new Thread(new Runnable() {
public void run() {
//这个线程,先握住s2这把锁,然后又握住s1这把锁,两把锁在身上
synchronized (s2){
//线程并发一起执行的,线程1被阻塞的同时,线程2拿到了s2这把锁,同时也被阻塞
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
s1.append("c");
s2.append("3");
//线程2现在需要s1这把锁,但是s1在线程1那里。程序也不是人,懂什么互相谦让,两个线程都没有第二把锁,进不去下面的步骤
//都在这里僵持着,耗着,就都阻塞了,这就形成了死锁
synchronized (s1){
s1.append("d");
s2.append("4");
System.out.println(s1);
System.out.println(s2);
}
}
}
}).start();
}
}