一:概述:
1.1什么是进程,什么是线程:
进程是一个应用程序,线程是一个应用单元。eg:现在java的dos命令里面最起码有main线程和垃圾回收线程
1.2进程和线程的关系:
进程A和进程B内存空间独立不共享,线程A和线程B堆内存和方法区内存共存,但是栈内存,一个线程一个栈。(堆和方法区共享,栈内存独立)
二:实现线程的方法:
第一种:编写一个新类,直接继承java.lang.thread;并且重写run方法
public class Yunxing2 {
public static void main(String[] args) {
MyThread myThread = new MyThread();//新建线程
myThread.start();//启动新线程
//作用:启动一个分支线程,在JVM中开辟一个新的栈空间,任务完成后,瞬间就结束了
//启动成功后,自动调用run方法,并且run方法跟main方法一样在栈空间的底部,同级别的
//这里的代码还是运行在主线中
myThread.run();//不用这个
//作用L不会启动线程,不会分配新的分支栈中,是单线程的run()方法
for(int i=0;i<1000;i++){
System.out.println("逐鹿线---->"+i);
}
}
}
//继承+重写run方法,main中new出一个该类,然后使用start启动一个新的线程
class MyThread extends Thread{//定义线程类
@Override
public void run() {
//编写程序,这段程序运行在分支线程中(分支)
for(int i=0;i<1000;i++) {
System.out.println("分支线程---->"+i);
}
}
}
第二种方法:编写一个类,实现java.lang.Runnable 接口(这种方法更好,因为一个类实现了一个接口,还可以继承别的类)
//定义一个可运行的类
public class MyRunnable implements Runnable{
public void run();
}
//创建一个线程对象Thread,并且传一个runnable参数
Thread t= new Thread(new MyRunnable());
//启动线程
t.start();
第三种方法:匿名内部类直接编写分支线程代码:
//main函数中直接写就好
Thread t=new Thread(new Runnable(){//在这里直接实现,这就是匿名内部类
@Override
public void run(){
for(int i=0;i<100;i++){
System.out.println("t线程---->"+i);
}
}
});
t.start();//启动支线线程
for(int i=0;i<100;i++){
System.out.printn("main线程--->"+i);
}
三:线程生命周期:
新建状态->就绪状态->运行状态->阻塞状态->死亡状态
四:线程的一些操作:
- 获取并且修改线程的名字
//用第一种创造线程的方式:
MyThread t= new MyThread();
MyThread.setname("新线程");
String tname=MyThread.getname();
- 获取当前线程对象:(有点类似于this指针)
//获取:static Thread currentThread();
//调用:Thread Thread.currrentThread();
Thread t=Thread.currentThread();
System.out.println(t.getname+"--->"+123123);
//如果这段代码出现在主线程中,当前线程就是主线程,
//当t1线程执行该代码,就是t1线程
//当t2线程执行该代码,就是t2线程
- 线程Sleep方法:
/*
static void sleep(long millis);
1.静态方法:Thread.sleep(1000);
2.参数:毫秒
3.作用:让当前线程进入休眠《进入“阻塞状态”,放弃占用CPU时间片
4.Thread.sleep();可以像计时器那样,间隔记录时间
*/
Thread t=new MyThread();
t.start();
try{
t.sleep(millis:1000*5);
}catch(InterruptedException e){
e.printStaclTrace();
}
- 中断睡眠方式:interrupt,但是这种终断睡眠的方式是依靠java的异常处理机制实现的
public static void main(String[] args) {
Thread t=new Thread(new MyRunnable2());
t.setName("t");
t.start();
//必须要有try,catch异常机制
try {
Thread.sleep(1000*5);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
t.interrupt();//这里中断支线程的sleep睡眠
}
//这个放在外面就好了,不要放在包创建的class里面
class MyRunnable2 implements Runnable{
@Override
public void run() {
// TODO Auto-generated method stub
try {
Thread.sleep(1000*60*60*30);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
- 结束线程:
//直接杀死线程,直接干掉这个线程(已过时,容易丢是数据)
跟上面的终止一样,就是最后哪里变成
t.stop();
//常用的结束线程,一般就是打一个bollean b标记,如果有问题,将这个变为false,想什么时候结束线程,将该类中b的值改为false,最后来一个return ;
- 线程调度:
- 实例方法:
void setPriority(int newPriority);
int getPriority()//获取线程有优先级
//最低优先级:1,最高优先级:10,默认优先级:5
//设置优先级:
Thread.currentThread().setPriority(1);
- 静态方法:
static void yield()//让位方法
//暂停当前正在执行的线程对象,yield()方法不是阻塞方法,让当前线程让位,让给其他线程使用
//yield()方法的执行会让当前线程从“运行状态”回到“就绪状态”
//注意:回到“就绪状态”还有可能再次强到。
//设置让位:
class MyRunnable6 implements Runnable{
@Override
public void run(){
for(int i=1;i<10000;i++){
//每100个让位一次
if(i%100==0){
Thread.yield();//当前线程让位
}
System.out.println(Thread.currentThread().getName()+"---->"+i);
}
}
}
- 线程合并join:同样的,join方法也是需要java异常机制的执行才可以
void join()
//合并线程
class MyThread1 extends Thread{
public void doSome(){
Mythread2 t=new MyThread2();
t.join();//当前线程受阻,t线程执行
}
}
- 多线程并发环境下,数据安全问题
- 重点:以后写代码需要在多线程并发的环境下考虑数据安全的问题
- 线程不安全的条件:满足下面三个条件就会有线程安全问题
- 多线程并发
- 有共享数据
- 共享数据有修改的行为
- 如何解决线程安全问题:使用线程同步机制,但是会损失一些效率,莫得办法
- 哪些数据有数据安全问题:实例变量和静态变量,局部变量没有安全问题
- 举例:编写两个线程对同一个账号读取并录入信息使用同步代码快synchronized
- 线程同步机制:synchronized
- 语法:一般也可以直接在构造方法的时候加入,这样就对类中其中的一种方法加上一个锁
synchronized(obj/this){
//线程同步代码快
//取款之前的余额
double before=this.getBalancce();
//取款之后的余额
double after=before-money;
//读取剩下的余额的信息
this.getBalancce();
}
synchronized后面的括号中传的这数据时相当关键的,
这个数据必须时多线程共享的数据,才能达到多线程排队
写什么?
那要看什么线程的同步,或者说填写共享对象
填写this或者你想同步的那几个线程的共享对象
- 原理:java语言当中,每个对象都有一把锁,也就相当于是一个标记,而使用synchronized传递对象的时候,只能同时占有一把锁,所以其他线程只能等候占用锁来继续执行下面的代码。
- 三种写法
第一种:同步代码块:
灵活
synchronized(线程共享对象){
t同步代码快
}
第二种:在实例方法中使用synchronized
表示共享对象一定时this
并且同步代码快时整个方法体
第三种:在静态方法上使用synchronized,保证静态变量的安全
表示找类锁
类锁永远只有一把
就算创建了100个对象,类锁也只有1个
public class Exam01{
public static void main(String[] args){
Myclass mc=new Myclass();
Thread t1=new MyThread(mc);
Thread t2=new MyThread(mc);
t1.setName("t1");
t2.setName("t2");
t1.start();
Thread.sleep(1000);
t2.start();
}
}
class MyThread extends Thread{
private Myclass=mc;
public void run(){
if(Thread.currentThread().getName().equals("t1")){
mc.dosome();
}
if(Thread.currentThread().getName().equals("t2")){
mc.doother();
}
}
}
class Myclass{
public synchronized void dosome(){//这是对象锁,如果前面加一个static,那就是类锁,不管创建了几个对象,都需要等待上一个线程执行完成,才能执行下一个步骤
System.out.println("dosome begin");
try{
Thread.sleep(1000*10);
}catch(InterruptedException e){
e.printStackTrace();
}
System.out.println("dosome over");
public void doother(){
System.println("doother begin");
System.println("doother over");
}
}
}
//注意:该线程使用类锁时,dosome有synchronized,但是doother没有synchronized,所以没有排序,两个异步处理,并没有同步,所以不安全
只有当两个全都有synchronized的时候才会有类锁性质顺序
- 死锁:直接上手,需要会写死锁
//一般是使用synchronized的时候,使用了嵌套的方法,可能会死锁,若是在死锁途中还进行睡眠操作,那将会导致程序停留崩溃
class MyThread1 extends Thread{
Obiect o1;
Object o2;
public void run(){
synchronized(o1){
synchronized(o2){
}
}
}
}
class MyThread2 extends Thread{
Obiect o1;
Object o2;
public void run(){//这里的两个object位置互换了一下
synchronized(o2){
synchronized(o1){
}
}
}
}
//以上代码,在main类中同步调用的时候,也就是MyThread1.start();MyThread2.start();会出现死锁现象,导致程序暂停。
- 如何解决线程安全问题?(这个之后再说吧)
1. 创建局部变量,没有安全问题
2. 创建多个实例对象,实例变量的内存就不共享了
3. 啥都不能用的话,只能适合用synchronized了